Skip to main content

Docker & Helm

Filament Management ships as three separate container images, built from three separate Dockerfiles at the repository root:

ImageDockerfileContains
BackendDockerfile.backendThe Go API server
FrontendDockerfile.frontendThe static React SPA, served by its own container
BridgeDockerfile.bridgeThe 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:

VariablePurpose
DB_DRIVERsqlite or mysql
DB_NAME, DB_HOST, DB_PORT, DB_USER, DB_PASSWORDDatabase connection
REGISTRATION_ENABLEDAllow self-service workspace registration (default false)
REDIS_ADDR, REDIS_DB, REDIS_PASSWORDRequired 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_MIGRATIONSRun 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_CONCURRENCYHow many jobs/tasks one worker runs in parallel (default 4)
HEALTH_ADDRListen address of the scheduler/worker health endpoint GET /healthz (default :8080)
SENTRY_DSN, SENTRY_ENVIRONMENT, SENTRY_TRACES_SAMPLE_RATEError 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 runs WORKER_CONCURRENCY items in parallel (default 4).

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.

warning

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).