Docker Compose
The repository ships Compose files under docker/prod/:
| File | What it runs |
|---|---|
docker-compose.yaml | One Faber instance, hardened, bound to localhost |
docker-compose.cluster.yaml | Two Faber replicas sharing one Redis for the step cache |
One instance
Section titled “One instance”export API_KEY="$(openssl rand -hex 32)"docker compose -f docker/prod/docker-compose.yaml up -dcurl -s http://localhost:3000/api/v1/healthThe service definition, abridged:
services: faber: image: ${FABER_IMAGE:-vgfractal/faber:latest} ports: - '${FABER_BIND_ADDRESS:-127.0.0.1}:${FABER_PORT:-3000}:3000' environment: - API_KEY=${API_KEY:?Set API_KEY before starting Faber} - CACHE_ENABLED=${CACHE_ENABLED:-false} - MAX_CONCURRENCY=${MAX_CONCURRENCY:-10} - MEMORY_MAX=${MEMORY_MAX:-256M} # ... the other limits, each overridable from the shell or an .env file privileged: true cgroup: host init: true read_only: true tmpfs: - /tmp:size=64m mem_limit: ${FABER_MEMORY_LIMIT:-8g} memswap_limit: ${FABER_MEMORY_LIMIT:-8g} pids_limit: ${FABER_PIDS_LIMIT:-4096} volumes: - /sys/fs/cgroup:/sys/fs/cgroup:rw stop_grace_period: 10s restart: unless-stopped healthcheck: test: ['CMD', 'curl', '--fail', '--silent', 'http://localhost:3000/api/v1/health'] security_opt: - no-new-privileges:trueOverride settings through the environment or an .env file next to the
Compose file:
| Variable | Default | Meaning |
|---|---|---|
FABER_IMAGE | vgfractal/faber:latest | Pin a version (vgfractal/faber:0.3.0) or use your own image |
FABER_BIND_ADDRESS, FABER_PORT | 127.0.0.1, 3000 | Where the port is published |
FABER_MEMORY_LIMIT, FABER_PIDS_LIMIT | 8g, 4096 | Container backstop; raise together with MAX_CONCURRENCY (see sizing) |
| Any Faber setting listed in the file | its default | Passed through to the container |
privileged, cgroup: host and the /sys/fs/cgroup volume are required;
the rest are hardening. Docker deployment explains each
one.
Your own image
Section titled “Your own image”Point the service at an image built from your Dockerfile (for example, one from the Dockerfile builder):
services: faber: image: my-registry/faber-judge:2026-10-18 build: context: . dockerfile: Dockerfile environment: - FABER_IMAGE_ID=my-registry/faber-judge:2026-10-18Keep uploaded files across restarts
Section titled “Keep uploaded files across restarts”The default file store lives in memory. For the filesystem backend, mount a
volume, because the container’s root is read-only:
services: faber: environment: - FABER_STORE_BACKEND=filesystem - FABER_STORE_PATH=/var/lib/faber/store volumes: - /sys/fs/cgroup:/sys/fs/cgroup:rw - faber-store:/var/lib/faber/store
volumes: faber-store:A cluster with a shared cache
Section titled “A cluster with a shared cache”docker/prod/docker-compose.cluster.yaml runs two Faber services and one
Redis on one host. It exists to try the shared step
cache and as a starting point; for real scale, run one
Faber per host (see running a cluster).
export API_KEY="$(openssl rand -hex 32)"docker compose -f docker/prod/docker-compose.cluster.yaml up -dWhat the file sets up:
- Redis on the Compose network only, no published port, used purely as a cache.
- Two Faber services, each with the same image and limits and
CACHE_ENABLED=true,CACHE_BACKEND=redis,CACHE_REDIS_URL=redis://redis:6379/0, and the sameCACHE_NAMESPACE, so a step one replica caches is a hit on the other. - Separate sandbox identity ranges. The two Faber processes share the
host kernel, so each needs its own
SANDBOX_IDENTITY_BASE(for example100000and200000withSANDBOX_IDENTITY_COUNT=65536). - Separate published ports, so you can address each replica and see a layer stored by one replayed by the other.
Size the host for both replicas: each reserves memory for its own
MAX_CONCURRENCY tasks.