If you run HonseFarm using Podman instead of docker-compose, you can leverage Podman’s systemd integration to:
- Auto-start containers at boot
- Automatically restart on failure
- Automatically pull and restart when images are updated
This provides a very Kubernetes-like workflow on a single host.
Prerequisites
- Images must already exist in a registry
- Built via GitHub Actions, or
- Manually pushed to your own registry
- Use named tags (e.g. :latest, :v1.2.3)
- Do not use digests or untagged images
- Podman with systemd support
- Root or appropriate permissions
How it works
Each logical group is defined as:
- A Pod YAML (*.yaml)
- A matching systemd kube unit (*.kube)
Location:
/etc/containers/systemd/
Enable units with:
systemctl daemon-reload
systemctl enable --now honsefarm-*.kube
Admin Panel Pod
/etc/containers/systemd/honsefarm-admin.yaml:
apiVersion: v1
kind: Pod
metadata:
name: honsefarm-admin
labels:
app: honsefarm-admin
annotations:
io.podman.annotations.publish-all: "true"
io.containers.autoupdate: "registry"
spec:
restartPolicy: Always
containers:
- name: adminpanel
image: ghcr.io/%REPOSITORY%/honse-honsefarm.adminpanel:latest
env:
- { name: ASPNETCORE_ENVIRONMENT, value: Production }
- { name: APPSETTINGS_PATH, value: /app/config/adminpanel.appsettings.Production.json }
- { name: ASPNETCORE_URLS, value: http://+:5100 }
ports:
- { containerPort: 5100 , hostPort: 5100 }
volumeMounts:
- { name: config, mountPath: /app/config, readOnly: true }
volumes:
- { name: config, hostPath: { path: /opt/honsefarm/config } }
/etc/containers/systemd/honsefarm-admin.kube:
[Unit]
Description=Honsefarm admin pod
Wants=network-online.target
After=network-online.target honsefarm-base.service
Requires=honsefarm-base.service
[Kube]
Yaml=honsefarm-admin.yaml
[Install]
WantedBy=multi-user.target
Core Pod (Server + Main Fileserver)
/etc/containers/systemd/honsefarm-core.yaml:
apiVersion: v1
kind: Pod
metadata:
name: honsefarm-core
labels:
app: honsefarm-core
annotations:
io.podman.annotations.publish-all: "true"
spec:
restartPolicy: Always
containers:
- name: server
image: ghcr.io/%REPOSITORY%/honse-honsefarm.server:latest
env:
- { name: ASPNETCORE_ENVIRONMENT, value: Production }
- { name: APPSETTINGS_PATH, value: /app/config/appsettings.Production.json }
- { name: ASPNETCORE_URLS, value: http://+:5000 }
- name: SERVER_ID
valueFrom: { secretKeyRef: { name: honsefarm-env, key: SERVER_ID } }
ports:
- { containerPort: 5000 , hostPort: 5000 }
- { containerPort: 4981 , hostPort: 4981 }
volumeMounts:
- { name: config, mountPath: /app/config, readOnly: true }
- { name: certs, mountPath: /app/certs }
# Requested: host ./data/census + ./data/profiles -> /opt/honsefarm/data/*
- { name: census, mountPath: /app/census }
- { name: profiles, mountPath: /app/profiles }
- name: main-fileserver
image: ghcr.io/%REPOSITORY%/honse-honsefarm.fileserver:latest
env:
- { name: ASPNETCORE_ENVIRONMENT, value: Production }
- { name: APPSETTINGS_PATH, value: /app/config/main-fileserver.appsettings.Production.json }
- { name: FILESERVER_ID, value: main-fileserver }
- { name: ASPNETCORE_URLS, value: http://+:5001 }
ports:
- { containerPort: 5001 , hostPort: 5001 }
- { containerPort: 4982 , hostPort: 4982 }
volumeMounts:
- { name: config, mountPath: /app/config, readOnly: true }
- { name: main-fileserver-cache, mountPath: /cache }
- { name: shared-files, mountPath: /data }
volumes:
- { name: config, hostPath: { path: /opt/honsefarm/config } }
- { name: certs, hostPath: { path: /opt/honsefarm/certs } }
- { name: main-fileserver-cache, hostPath: { path: /opt/honsefarm/main_fileserver_cache } }
- { name: shared-files, hostPath: { path: /opt/honsefarm/shared_files } }
- { name: census, hostPath: { path: /opt/honsefarm/data/census } }
- { name: profiles, hostPath: { path: /opt/honsefarm/data/profiles } }
/etc/containers/systemd/honsefarm-core.kube:
[Unit]
Description=Honsefarm core pod
Wants=network-online.target
After=network-online.target honsefarm-base.service
Requires=honsefarm-base.service
[Kube]
Yaml=honsefarm-core.yaml
[Install]
WantedBy=multi-user.target
Infra Pod (Postgres, Redis, Traefik)
/etc/containers/systemd/honsefarm-infra.yaml:
apiVersion: v1
kind: Pod
metadata:
name: honsefarm-infra
labels: { app: honsefarm-infra }
annotations: { io.podman.annotations.publish-all: "true" }
spec:
restartPolicy: Always
containers:
- name: postgres
image: docker.io/library/postgres:16
env:
- { name: POSTGRES_DB, value: honsefarm }
- { name: POSTGRES_USER, value: honsefarm }
- name: POSTGRES_PASSWORD
valueFrom: { secretKeyRef: { name: honsefarm-env, key: DB_PASSWORD } }
ports:
- { containerPort: 5432 , hostPort: 5432 }
volumeMounts:
- { name: postgres-data, mountPath: /var/lib/postgresql/data }
- name: redis
image: docker.io/library/redis:7-alpine
args: [ "redis-server", "--requirepass", "$(REDIS_PASSWORD)" ]
env:
- name: REDIS_PASSWORD
valueFrom: { secretKeyRef: { name: honsefarm-env, key: REDIS_PASSWORD } }
ports:
- { containerPort: 6379 , hostPort: 6379 }
volumeMounts:
- { name: redis-data, mountPath: /data }
- name: traefik
image: docker.io/library/traefik:v3.0
args:
- --providers.file.directory=/etc/traefik/dynamic
- --providers.file.watch=true
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --certificatesresolvers.letsencrypt.acme.httpchallenge=true
- --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web
- --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
- --entrypoints.web.http.redirections.entrypoint.to=websecure
- --entrypoints.web.http.redirections.entrypoint.scheme=https
- --entrypoints.websecure.address=:443
- --accesslog=true
- --api.dashboard=true
- --api.insecure=false
- --log.level=WARNING
# Email via env
- --certificatesresolvers.letsencrypt.acme.email=$(EMAIL)
env:
- name: EMAIL
valueFrom: { secretKeyRef: { name: honsefarm-env, key: EMAIL } }
ports:
- { containerPort: 80 , hostPort: 80 }
- { containerPort: 443 , hostPort: 443 }
volumeMounts:
- { name: traefik-dynamic, mountPath: /etc/traefik/dynamic, readOnly: true }
- { name: letsencrypt, mountPath: /letsencrypt }
volumes:
- { name: postgres-data, hostPath: { path: /opt/honsefarm/postgres_data } }
- { name: redis-data, hostPath: { path: /opt/honsefarm/redis_data } }
- { name: letsencrypt, hostPath: { path: /opt/honsefarm/letsencrypt } }
- { name: traefik-dynamic, hostPath: { path: /opt/honsefarm/traefik/dynamic } }
/etc/containers/systemd/honsefarm-infra.kube:
[Unit]
Description=Honsefarm infra pod
Wants=network-online.target
After=network-online.target honsefarm-base.service
Requires=honsefarm-base.service
[Kube]
Yaml=honsefarm-infra.yaml
[Install]
WantedBy=multi-user.target
Observability Pod (VictoriaMetrics + Grafana + VictoriaLogs)
Change the %ADMINURL% to the https://admin.* address for your server instance.
/etc/containers/systemd/honsefarm-observability.yaml:
apiVersion: v1
kind: Pod
metadata:
name: honsefarm-observability
labels: { app: honsefarm-observability }
annotations: { io.podman.annotations.publish-all: "true" }
spec:
restartPolicy: Always
containers:
- name: victoriametrics
image: victoriametrics/victoria-metrics:latest
args:
- -promscrape.config=/etc/victoriametrics/scrape-config.yml
- -storageDataPath=/victoria-metrics-data
- -retentionPeriod=12
- -httpListenAddr=:8428
- -influxListenAddr=:8089
ports:
- { containerPort: 8428 , hostPort: 8428 }
- { containerPort: 8089 , hostPort: 8089 }
volumeMounts:
- { name: vm-config, mountPath: /etc/victoriametrics, readOnly: true }
- { name: vm-data, mountPath: /victoria-metrics-data }
- name: grafana
image: grafana/grafana:latest
env:
- { name: GF_SECURITY_ADMIN_USER, value: admin }
- { name: GF_SECURITY_ADMIN_PASSWORD, value: admin123 }
- { name: GF_PATHS_PROVISIONING, value: /etc/grafana/provisioning }
- { name: GF_SERVER_ROOT_URL, value: "%ADMINURL%/grafana" }
- { name: GF_SERVER_SERVE_FROM_SUB_PATH, value: "true" }
ports:
- { containerPort: 3000 , hostPort: 3000 }
volumeMounts:
- { name: grafana-provisioning, mountPath: /etc/grafana/provisioning, readOnly: true }
- { name: grafana-dashboards, mountPath: /var/lib/grafana/dashboards, readOnly: true }
- { name: grafana-data, mountPath: /var/lib/grafana }
- name: victorialogs
image: docker.io/victoriametrics/victoria-logs:latest
args:
- -storageDataPath=/victoria-logs-data
- -retentionPeriod=7d
- -httpListenAddr=:9428
ports:
- { containerPort: 9428 , hostPort: 9428 }
volumeMounts:
- { name: vl-data, mountPath: /victoria-logs-data }
- name: victoriatraces
image: victoriametrics/victoria-traces:latest
args:
- -storageDataPath=/victoria-traces-data
- -retentionPeriod=7d
- -http.pathPrefix=/traces
ports:
- { containerPort: 10428 , hostPort: 10428 }
volumeMounts:
- { name: vt-data, mountPath: /victoria-traces-data }
volumes:
- { name: vm-config, hostPath: { path: /opt/honsefarm/monitoring/victoriametrics } }
- { name: vm-data, hostPath: { path: /opt/honsefarm/vm_data } }
- { name: grafana-provisioning, hostPath: { path: /opt/honsefarm/monitoring/grafana/provisioning } }
- { name: grafana-dashboards, hostPath: { path: /opt/honsefarm/monitoring/grafana/dashboards } }
- { name: grafana-data, hostPath: { path: /opt/honsefarm/grafana_data } }
- { name: vl-data, hostPath: { path: /opt/honsefarm/vl_data } }
- { name: vt-data, hostPath: { path: /opt/honsefarm/vt_data } }
/etc/containers/systemd/honsefarm-observability.kube:
[Unit]
Description=Honsefarm observability pod
Wants=network-online.target
After=network-online.target honsefarm-base.service
Requires=honsefarm-base.service
[Kube]
Yaml=honsefarm-observability.yaml
[Install]
WantedBy=multi-user.target
PostgreSQL backup pod (pg_basebackup + config/certs copy)
Note: Backup seems to need a bit more work, but have not gotten around to fixing those.
/etc/containers/systemd/honsefarm-backup.yaml:
apiVersion: v1
kind: Pod
metadata:
name: honsefarm-backup
labels: { app: honsefarm-backup }
spec:
hostNetwork: true
restartPolicy: Never
containers:
- name: postgres-backup
image: docker.io/library/postgres:16
env:
- name: PGPASSWORD
valueFrom: { secretKeyRef: { name: honsefarm-env, key: DB_PASSWORD } }
- { name: PGUSER, value: honsefarm }
- { name: PGHOST, value: 127.0.0.1 }
- { name: PGPORT, value: "5432" }
- { name: PGSSLMODE, value: prefer }
command:
- /bin/sh
- -c
- |
set -euo pipefail
BACKUP_ROOT=/opt/HonseFarm/backup
TS=$(date -u +%Y%m%d)
WORKDIR=$(mktemp -d)
trap 'rm -rf "$WORKDIR"' EXIT
mkdir -p "$BACKUP_ROOT"
if [ -d /opt/HonseFarm/config ]; then
cp -a /opt/HonseFarm/config "$WORKDIR/config"
fi
if [ -d /opt/HonseFarm/certs ]; then
cp -a /opt/HonseFarm/certs "$WORKDIR/certs"
fi
pg_basebackup -D "$WORKDIR/pgdata" -Fp -Xs -P
tar -C "$WORKDIR" -czf "$BACKUP_ROOT/honsefarm-backup-$TS.tar.gz" .
ls -1t "$BACKUP_ROOT"/honsefarm-backup-*.tar.gz 2>/dev/null | tail -n +6 | xargs -r rm -f
volumeMounts:
- { name: honsefarm-root, mountPath: /opt/HonseFarm }
volumes:
- { name: honsefarm-root, hostPath: { path: /opt/honsefarm } }
Base Services & Secrets
This last file is a bit of a molloch, it contains services for external and internal use to make sure the firewalls in podman can auto assign, so hopefully no need to touch the firewall on the host 🤞
It also contains the secrets needed in all the other files.
/etc/containers/systemd/honsefarm-base.yaml:
apiVersion: v1
kind: Service
metadata: { name: server-svc }
spec:
selector: { app: honsefarm-core }
ports:
- { name: http, port: 5000, targetPort: 5000 }
- { name: metrics, port: 4981, targetPort: 4981 }
---
apiVersion: v1
kind: Service
metadata: { name: main-fileserver-svc }
spec:
selector: { app: honsefarm-core }
ports:
- { name: http, port: 5001, targetPort: 5001 }
- { name: metrics, port: 4982, targetPort: 4982 }
---
apiVersion: v1
kind: Service
metadata: { name: honsefarm-shard-fileserver }
spec:
selector: { app: honsefarm-shard-fileserver }
ports:
- { name: http, port: 5002, targetPort: 5002 }
- { name: metrics, port: 4983, targetPort: 4983 }
---
apiVersion: v1
kind: Service
metadata: { name: traefik-svc }
spec:
selector: { app: honsefarm-infra }
ports:
- { name: web, port: 80, targetPort: 80 }
- { name: websecure, port: 443, targetPort: 443 }
---
apiVersion: v1
kind: Service
metadata: { name: adminpanel-svc }
spec:
selector: { app: honsefarm-admin }
ports:
- { name: http, port: 5100, targetPort: 5100 }
---
apiVersion: v1
kind: Service
metadata: { name: victoriametrics-svc }
spec:
selector: { app: honsefarm-observability }
ports:
- { name: http, port: 8428, targetPort: 8428 }
---
apiVersion: v1
kind: Service
metadata: { name: grafana-svc }
spec:
selector: { app: honsefarm-observability }
ports:
- { name: http, port: 3000, targetPort: 3000 }
---
apiVersion: v1
kind: Service
metadata: { name: victorialogs-svc }
spec:
selector: { app: honsefarm-observability }
ports:
- { name: http, port: 9428, targetPort: 9428 }
---
apiVersion: v1
kind: Service
metadata: { name: postgres }
spec:
selector: { app: honsefarm-infra }
ports:
- { name: postgres, port: 5432, targetPort: 5432 }
---
apiVersion: v1
kind: Service
metadata: { name: redis }
spec:
selector: { app: honsefarm-infra }
ports:
- { name: redis, port: 6379, targetPort: 6379 }
---
apiVersion: v1
kind: Service
metadata: { name: server }
spec:
selector: { app: honsefarm-core }
ports:
- { name: http, port: 5000, targetPort: 5000 }
---
apiVersion: v1
kind: Service
metadata: { name: adminpanel }
spec:
selector: { app: honsefarm-admin }
ports:
- { name: http, port: 5100, targetPort: 5100 }
---
apiVersion: v1
kind: Service
metadata: { name: victoriametrics }
spec:
selector: { app: honsefarm-observability }
ports:
- { name: http, port: 8428, targetPort: 8428 }
---
apiVersion: v1
kind: Service
metadata: { name: grafana }
spec:
selector: { app: honsefarm-observability }
ports:
- { name: http, port: 3000, targetPort: 3000 }
---
apiVersion: v1
kind: Service
metadata: { name: victorialogs }
spec:
selector: { app: honsefarm-observability }
ports:
- { name: http, port: 9428, targetPort: 9428 }
---
apiVersion: v1
kind: Service
metadata: { name: victoriatraces }
spec:
selector: { app: honsefarm-observability }
ports:
- { name: http, port: 10428, targetPort: 10428 }
---
apiVersion: v1
kind: Secret
metadata: { name: honsefarm-env }
type: Opaque
data:
DB_PASSWORD: postgres pw as in config / .env -> in base64
REDIS_PASSWORD: redispw as in config / .env -> in base64
SERVER_ID: ServerID as in config / .env -> in base64
DOMAIN: Domain as in config / .env-> in base64
EMAIL: c3VwZXJzaWxseXBhc3N3b3Jk