docker-compose.yml
Rama main · réplica de lectura del repositorio original.
services:
jobd:
# Pulled from GHCR, NOT built here. There used to be `build: .` + `image:
# jobd:latest` with no registry behind that tag, which meant `docker compose pull`
# was a silent no-op and a bare `docker compose up -d` after a `git pull` happily
# reused the stale local image — you restarted the OLD code and nothing said so.
# The workaround was to remember `--build` forever. A registry-backed, version-
# pinned tag deletes that whole class of mistake instead of guarding against it.
#
# JOBD_TAG pins the EXACT version (e.g. 0.5.18). Production should never follow
# `latest`: scripts/deploy-broker.sh resolves the newest release, writes JOBD_TAG
# into .env, and can roll back to the previous pin if the new image fails its
# health gate — none of which is possible against a moving tag. The `latest`
# default here exists only so a fresh clone can start without ceremony.
#
# Building from source (a fork, or a local change):
# docker compose -f docker-compose.yml -f docker-compose.build.yml up -d --build
image: ghcr.io/musharna/jobd:${JOBD_TAG:-latest}
container_name: jobd
restart: unless-stopped
# PERMISSIONS: the image runs as a non-root user (uid 10001, see Dockerfile).
# The bind-mounted ./data + ./logs below must be WRITABLE by that user, or the
# broker crash-loops on "sqlite3.OperationalError: attempt to write a readonly
# database". Two fixes — pick one:
# (a) chown the dirs to the image user: chown -R 10001:10001 ./data ./logs
# (b) run the container as your host user so the files stay host-owned (nicer
# for backups/log-tailing): uncomment the line below, replacing 1000:1000
# with your own `id -u`:`id -g`.
# user: "1000:1000"
# SECURITY: host networking + JOBD_HOST binding is the access-control model.
# Docker's default userland-proxy NATs all cross-host inbound source IPs to
# the bridge gateway, which defeats the source-IP ACL. With network_mode:
# host, uvicorn binds DIRECTLY to JOBD_HOST (a tailscale IP) on the host's
# network namespace — only requests arriving on the tailscale interface
# reach the broker, and source IPs are preserved for the ACL to inspect.
# JOBD_HOST MUST be a loopback (127.0.0.1) or tailscale CGNAT (100.64.0.0/10)
# address — never 0.0.0.0 or a public-facing interface. tests/test_deploy_lint.py
# enforces this at CI.
network_mode: host
volumes:
- ./config:/app/config:ro
- ./data:/app/data
- ./logs:/app/logs
environment:
JOBD_CONFIG_DIR: /app/config
JOBD_DB_URL: sqlite:////app/data/jobd.db
JOBD_LOGS_DIR: /app/logs
# Set to your host's tailscale CGNAT IP (100.64.0.0/10) for cross-host
# access, or leave at 127.0.0.1 for a single-host (loopback-only) broker.
# MUST NOT be 0.0.0.0 or a public interface — see the SECURITY note above.
JOBD_HOST: ${JOBD_HOST:-127.0.0.1}
JOBD_PORT: 8765
JOBD_API_TOKEN: ${JOBD_API_TOKEN:?JOBD_API_TOKEN must be set in .env (see docs/security.md)}