Docker & Helm
Filament Management ships as three separate container images, built from three separate Dockerfiles at the repository root:
| Image | Dockerfile | Contains |
|---|---|---|
| Backend | Dockerfile.backend | The Go API server |
| Frontend | Dockerfile.frontend | The static React SPA, served by its own container |
| Bridge | Dockerfile.bridge | The local network bridge binary |
All three are minimal, statically-linked, distroless-style builds (CGO_ENABLED=0, scratch-based
final stage) for a small attack surface and small image size.
Configuration
The backend is configured entirely through environment variables. The ones most deployments care about:
| Variable | Purpose |
|---|---|
DB_DRIVER | sqlite or mysql |
DB_NAME, DB_HOST, DB_PORT, DB_USER, DB_PASSWORD | Database connection |
REGISTRATION_ENABLED | Allow self-service workspace registration (default false) |
REDIS_ADDR, REDIS_DB, REDIS_PASSWORD | Required when running more than one backend replica. Routes local network bridge requests across pods and connects the API to the scheduler and worker processes (see below) |
EXECUTE_MIGRATIONS | Run database migrations at startup (default true). Set to false for the scheduler and worker so only one process type migrates, or for every process when migrations run separately (see Database migrations) |
WORKER_CONCURRENCY | How many jobs/tasks one worker runs in parallel (default 4) |
HEALTH_ADDR | Listen address of the scheduler/worker health endpoint GET /healthz (default :8080) |
SENTRY_DSN, SENTRY_ENVIRONMENT, SENTRY_TRACES_SAMPLE_RATE | Error reporting (global, not per-workspace — see Multi-tenancy) |
With a single backend replica, Redis isn't needed at all. REDIS_ADDR only becomes necessary once
you scale beyond one pod.
Background jobs: scheduler and worker
Besides answering API requests, the backend does work in the background:
- Periodic jobs: removing expired sessions, and the hourly check for OpenEPaperLink tags that rebooted.
- Tasks triggered by a request: printing labels, rendering display images (pushes to OpenEPaperLink as well as the public image and JSON endpoints that displays poll), and creating spools, vendors and filaments (including the OpenPrintTag import).
Where this runs depends on whether Redis is configured:
- Without Redis, the API server does all of it itself. This is the single-instance setup, and nothing extra needs to run.
- With Redis, the API server only queues the work. Two extra process types, started from the
same backend image with a different first argument, carry it out:
scheduler: all scheduler instances compete for a lease key in Redis, and the one holding it is the leader. Only the leader decides which periodic jobs are due and queues them. If the leader stops, its lease expires within 30 seconds and another scheduler instance takes over. A leader that shuts down cleanly hands over right away.worker: takes queued jobs and tasks from Redis and runs them. Each one executes on exactly one worker, however many workers you run. Request-triggered tasks are always picked up before periodic jobs. Each worker runsWORKER_CONCURRENCYitems in parallel (default4).
For a task triggered by a request (for example creating a spool), the API server waits for the
worker's result and answers with it, so clients see no difference. If no worker picks the task up
in time, the request fails with 504 and the task is discarded, not run later.
Workers talk to your printer, OpenEPaperLink access point and Home Assistant. If you don't use the local network bridge, the workers need direct network access to them, just like the API server does in a single-instance setup.
Once REDIS_ADDR is set, run at least one scheduler and one worker. Without a worker, label
printing, display images and pushes, and creating spools, vendors or filaments all fail, and no
periodic jobs run. The Compose file and the Helm chart both do this for you.
With Docker Compose, the compose.yaml in the repository already contains the redis,
scheduler (command: scheduler) and worker (command: worker) services. To run a single
container without Redis instead, remove those three services and the REDIS_ADDR line.
Database migrations
By default the API server applies any pending database migrations when it starts. To run them as
a separate step instead (for example before starting several API replicas, which would otherwise
race each other), set EXECUTE_MIGRATIONS=false on every process and run the backend image with
the migrate command:
docker compose run --rm backend migrate
migrate applies all pending migrations and exits. Its exit code is non-zero if they fail. If the
database isn't accepting connections yet, it keeps retrying for up to five minutes; change this
with --wait, for example migrate --wait 10m.
Helm chart
A Helm chart lives under helm/ in the repository and deploys the backend and frontend as separate
Deployments, with an optional MariaDB dependency. When redis.addr is set, it also deploys the
scheduler and worker as two more Deployments of the backend image. Key values:
image:
repositoryBackend: jonasled.dev/jonasled/filamentmanagement/backend
repositoryFrontend: jonasled.dev/jonasled/filamentmanagement/frontend
tag: "" # defaults to the chart's appVersion
replicaCountBackend: 1
replicaCountFrontend: 1
mariadb:
enabled: true
external:
host: mariadb
auth:
database: filamentmanagement
username: filamentmanagement
registration:
enabled: false
redis:
addr: "" # set this once replicaCountBackend > 1
# Only deployed when redis.addr is set
scheduler:
replicaCount: 2 # one leader plus a hot standby
worker:
replicaCount: 1
ingress:
enabled: false
Install or upgrade with:
helm dependency update helm
helm upgrade --install filamentmanagement ./helm \
--set image.tag=<version> \
--set ingress.enabled=true \
--set ingress.hosts[0].host=filaments.example.com
Migrations in the Helm chart
The chart runs database migrations in a Kubernetes Job (the backend image with the migrate
command) as a Helm hook, after helm install and after every helm upgrade. The API, scheduler
and worker pods don't migrate on startup. Helm waits for the Job to finish. If it fails, the
install or upgrade is reported as failed and the Job is kept so you can read its logs with
kubectl logs job/<release>-filamentmanagement-migrate. Successful Jobs are removed.
migrations:
job:
enabled: true
hooks: "post-install,post-upgrade"
waitTimeout: 5m # how long to retry while the database is still starting
By default the Job runs after an upgrade has rolled out, so new pods briefly run against the old
schema. To migrate first, while the old pods keep serving, set hooks to
"post-install,pre-upgrade". A fresh install always needs post-install, because the bundled
MariaDB only exists once the release has been created.
Set migrations.job.enabled: false to go back to the API pods migrating on startup.
See helm/values.yaml for the full set of configurable values (resources, autoscaling, pod
security context, service account, and more — all fairly standard for a Helm chart following the
default chart layout).