Skip to content

Docker Compose contract

compose.yaml defines one service named agent. The same file supports a source build and an explicitly configured prebuilt image.

Resolve the configuration

Before every first start or configuration change:

docker compose -f compose.yaml -f compose.prod.yaml config --quiet

To validate a different environment file:

ENV_FILE=.env.production docker compose --env-file .env.production \
  -f compose.yaml -f compose.prod.yaml config --quiet

Compose interpolation and container environment injection are separate. Pass the same file through --env-file and ENV_FILE when its name is not .env.

Source-build path

The default image name is blacki:local, and the service includes build: ..

docker compose -f compose.yaml -f compose.prod.yaml up --build -d

Use this path unless a registry image and its access policy have been verified.

Prebuilt-image path

Set an exact registry reference:

IMAGE=ghcr.io/your-owner/blacki:your-tag

Then:

docker compose -f compose.yaml -f compose.prod.yaml pull
docker compose -f compose.yaml -f compose.prod.yaml up --no-build -d

The --no-build flag prevents an accidental local rebuild under the registry tag.

Network defaults

The mapping is:

ports:
  - "127.0.0.1:${HOST_PORT:-8080}:8080"

The process listens on 0.0.0.0:8080 inside the container, while the VPS publishes it only on loopback. Telegram long polling needs no inbound port.

The base file publishes no host port. compose.prod.yaml adds the loopback mapping and forces the web interface and reload off even if hostile shell or .env values request them. For local browser development, use compose.dev.yaml; it enables the development features but also forces loopback. Use an SSH tunnel for temporary remote access. Neither overlay provides authentication for public exposure.

Persistent mounts

Host Container Data
./.adk_state /app/src/.adk SQLite and ADK artifacts
./data /app/data Optional local memory data
./logs /app/logs Application JSON logs and traces

The entrypoint starts as root only long enough to create and assign these bind mounts, then executes the server as the non-root app user.

Health probes

The healthcheck uses Python's standard HTTP client to call /ready inside the container. Readiness returns HTTP 503 until SQLite is initialized and whenever its side-effect-free SELECT 1 check fails. /live checks only that the application event loop is serving requests. Optional Mem0 is not readiness-critical.

docker compose -f compose.yaml -f compose.prod.yaml ps

/health remains a compatibility alias for /ready.

Lifecycle

# Attached source build
docker compose -f compose.yaml -f compose.dev.yaml up --build

# Detached source build
docker compose -f compose.yaml -f compose.prod.yaml up --build -d

# Status
docker compose -f compose.yaml -f compose.prod.yaml ps

# Logs
docker compose -f compose.yaml -f compose.prod.yaml logs --follow agent

# Restart
docker compose -f compose.yaml -f compose.prod.yaml restart agent

# Stop and remove the container and network
docker compose -f compose.yaml -f compose.prod.yaml down

docker compose down does not delete the bind-mounted host directories.

Source changes

Compose Watch is not configured. Rebuild after source or dependency changes:

docker compose -f compose.yaml -f compose.dev.yaml up --build

For faster Python iteration, use the local uv workflow.