Docker
Single-container
Section titled “Single-container”docker run -d --name z4j-brain \ -p 8080:7700 \ -e Z4J_DATABASE_URL=postgresql+asyncpg://z4j:pw@db:5432/z4j \ -e Z4J_SECRET=... \ -e Z4J_SESSION_SECRET=... \ -e Z4J_AUDIT_SECRET=... \ -e Z4J_PUBLIC_URL=https://z4j.example.com \ z4jdev/z4j:latestDocker Compose (with Postgres)
Section titled “Docker Compose (with Postgres)”services: db: image: postgres:18-alpine environment: POSTGRES_DB: z4j POSTGRES_USER: z4j POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} volumes: - z4j-db:/var/lib/postgresql/data restart: unless-stopped
brain: image: z4jdev/z4j:latest depends_on: [db] environment: Z4J_DATABASE_URL: postgresql+asyncpg://z4j:${POSTGRES_PASSWORD}@db/z4j Z4J_SECRET: ${Z4J_SECRET} Z4J_SESSION_SECRET: ${Z4J_SESSION_SECRET} Z4J_AUDIT_SECRET: ${Z4J_AUDIT_SECRET} Z4J_PUBLIC_URL: https://z4j.example.com ports: ["8080:7700"] restart: unless-stopped
volumes: z4j-db:Image layout
Section titled “Image layout”- Base:
python:3.14-slim-trixie(Debian 13). - Size: < 250 MiB.
- Entry point:
z4j-brain serve(FastAPI via Uvicorn). - Signal handling:
SIGTERMtriggers graceful shutdown.
Running migrations
Section titled “Running migrations”Migrations run automatically on container start (idempotent; safe on every boot). To run them manually:
docker exec -it z4j-brain z4j-brain migrateHealthcheck
Section titled “Healthcheck”healthcheck: test: ["CMD", "wget", "-qO-", "http://localhost:7700/api/v1/health"] interval: 30s timeout: 5s retries: 3JSON to stdout. Aggregate with your log shipper.
First boot
Section titled “First boot”Watch stderr on first boot to capture the setup URL. With Compose:
docker compose logs -f brain 2>&1 | grep "setup URL"