# ── Timmy Time — docker-compose ───────────────────────────────────────────── # # Services # dashboard FastAPI app + swarm coordinator (always on) # agent Swarm worker template — scale with: # docker compose up --scale agent=N --profile agents # # Volumes # timmy-data Shared SQLite (data/swarm.db + data/timmy.db) # # Usage # make docker-build build the image # make docker-up start dashboard only # make docker-agent add one agent worker # make docker-down stop everything # make docker-logs tail logs services: # ── Dashboard (coordinator + FastAPI) ────────────────────────────────────── dashboard: build: . image: timmy-time:latest container_name: timmy-dashboard # Run as root in the dev compose because bind-mounted host files # (./src, ./static) may not be readable by the image's non-root # "timmy" user — this is the #1 cause of 403 errors on macOS. # Production (docker-compose.prod.yml) uses no bind mounts and # correctly runs as the Dockerfile's non-root USER. user: "0:0" ports: - "8000:8000" volumes: - timmy-data:/app/data - ./src:/app/src # live-reload: source changes reflect immediately - ./static:/app/static # live-reload: CSS/asset changes reflect immediately environment: DEBUG: "true" # Point to host Ollama (Mac default). Override in .env if different. OLLAMA_URL: "${OLLAMA_URL:-http://host.docker.internal:11434}" # Grok (xAI) — opt-in premium cloud backend GROK_ENABLED: "${GROK_ENABLED:-false}" XAI_API_KEY: "${XAI_API_KEY:-}" GROK_DEFAULT_MODEL: "${GROK_DEFAULT_MODEL:-grok-3-fast}" extra_hosts: - "host.docker.internal:host-gateway" # Linux compatibility networks: - swarm-net restart: unless-stopped healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/health"] interval: 30s timeout: 5s retries: 3 start_period: 30s # ── Timmy — sovereign AI agent (separate container) ─────────────────────── timmy: build: . image: timmy-time:latest container_name: timmy-agent volumes: - timmy-data:/app/data - ./src:/app/src environment: COORDINATOR_URL: "http://dashboard:8000" OLLAMA_URL: "${OLLAMA_URL:-http://host.docker.internal:11434}" TIMMY_AGENT_ID: "timmy" extra_hosts: - "host.docker.internal:host-gateway" command: ["python", "-m", "timmy.docker_agent"] networks: - swarm-net depends_on: dashboard: condition: service_healthy restart: unless-stopped # ── Agent worker template ─────────────────────────────────────────────────── # Scale horizontally: docker compose up --scale agent=4 --profile agents # Each container gets a unique AGENT_ID via the replica index. agent: build: . image: timmy-time:latest profiles: - agents volumes: - timmy-data:/app/data - ./src:/app/src environment: COORDINATOR_URL: "http://dashboard:8000" OLLAMA_URL: "${OLLAMA_URL:-http://host.docker.internal:11434}" AGENT_NAME: "${AGENT_NAME:-Worker}" AGENT_CAPABILITIES: "${AGENT_CAPABILITIES:-general}" extra_hosts: - "host.docker.internal:host-gateway" command: ["sh", "-c", "python -m swarm.agent_runner --agent-id agent-$(hostname) --name $${AGENT_NAME:-Worker}"] networks: - swarm-net depends_on: dashboard: condition: service_healthy restart: unless-stopped # ── Shared volume ───────────────────────────────────────────────────────────── # NOTE: the data/ directory must exist before running docker compose up. # `make docker-up` and `make up` handle this automatically. # If running docker compose directly, first run: mkdir -p data volumes: timmy-data: driver: local driver_opts: type: none o: bind device: "${PWD}/data" # ── Internal network ────────────────────────────────────────────────────────── networks: swarm-net: driver: bridge