This document describes how docker-compose files are managed in relation to the configuration system.
Instead of generating docker-compose files from YAML config, we maintain the compose files directly with references to the config system:
- Infrastructure (
infra/docker/infrastructure.yml) - PostgreSQL, Keycloak, Kong, shared networks/volumes. - API Services (
infra/docker/services-api.yml) - llm-api, media-api, response-api. - MCP Services (
infra/docker/services-mcp.yml) - mcp-tools, vector-store, sandbox helpers. - Observability (
infra/docker/observability.yml) - Prometheus, Grafana, Jaeger, OTEL collector. - Inference (
infra/docker/inference.yml) - vLLM GPU/CPU profiles. - Development overlay (
infra/docker/dev-full.yml) - addshost.docker.internalmapping for hybrid workflows.
The root docker-compose.yml stitches the profiles together (infrastructure + services + MCP + observability). Profiles such as full, mcp, monitor, and dev-full map directly to the files above.
Each service loads the entire root .env via Compose's env_file directive, then layers per-variable ${VAR:-default} overrides on top. The env_file path is itself indirected through ENV_FILE so it always resolves to the single root .env unless explicitly overridden:
services:
llm-api:
# Load every key/value from the root .env into the container
env_file:
- ${ENV_FILE:-../../.env}
environment:
# Database - constructed DSN from .env values
DB_POSTGRESQL_WRITE_DSN: "postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@api-db:5432/${POSTGRES_DB}?sslmode=disable"
# Per-variable overrides fall back to defaults that mirror
# config/defaults.yaml and packages/go-common/config/types.go
HTTP_PORT: ${HTTP_PORT:-8080}
LOG_LEVEL: ${LOG_LEVEL:-info}The root .env is generated from .env.template by make setup / make quickstart. There is exactly one .env at the repository root---no docker/.env and no per-environment config/<env>.env files.
- Simplicity - Docker Compose is already declarative and easy to read
- Flexibility - Allows docker-specific optimizations (healthchecks, extra hosts, bind mounts)
- Version Control - Changes are clearly visible in git diffs
- No Generation Overhead - No build step required
- Profiles - Different dev/prod/monitoring stacks can be launched with a single
maketarget
If needed, a generator can be built using packages/go-common/config/compose/ that:
- Reads
config/defaults.yaml - Applies environment overrides
- Generates docker-compose YAML files
- Validates output
To validate compose files:
# Validate syntax
docker compose -f docker/infrastructure.yml config
# Validate with current environment
docker compose -f docker-compose.yml config
# Dry-run full stack
docker compose --profile full config
# Verify dev overlay
docker compose --profile dev-full configTypical networks and volumes:
- Networks:
jan-server_default(core),jan-server_mcp-network(MCP helpers) - Volumes:
api-db-data,keycloak-db-data,vector-store-data,grafana-data
All of the above are declared in the compose snippets so they can be inspected with docker compose config.
Rationale: Direct maintenance is simpler and more maintainable than generation for this use case. The generator infrastructure exists in packages/go-common/config/compose/ if needed in the future.