services: # Traefik deliberately does NOT live in this stack — it's shared infra fronting # Gitea/Portainer/Matrix/agent (see ~/traefik/docker-compose.yml, a separate, # independently-managed stack). It used to be a service here, but a GitOps redeploy # tears every service in a stack down before bringing them back up — and claude-agent's # image pull goes through Traefik→Gitea's registry, so a self-hosted Traefik ends up # briefly tearing down the very route its sibling service needs to come back up. # Circular dependency, self-inflicted outage. Don't put Traefik back in this file. matrix-homeserver: image: ghcr.io/continuwuity/continuwuity:latest container_name: matrix-homeserver restart: unless-stopped environment: CONTINUWUITY_SERVER_NAME: ${MATRIX_SERVER_NAME} CONTINUWUITY_DATABASE_PATH: /var/lib/continuwuity CONTINUWUITY_ADDRESS: 0.0.0.0 CONTINUWUITY_PORT: 8008 # Private control-room bot only — no federation, no open registration. # Registration is flipped on temporarily, once, to create the bot account # (see README "First boot: Matrix bot account"). CONTINUWUITY_ALLOW_FEDERATION: "false" CONTINUWUITY_ALLOW_REGISTRATION: ${MATRIX_ALLOW_REGISTRATION:-false} # CONTINUWUITY_REGISTRATION_TOKEN is deliberately NOT set here: Continuwuity treats # a present-but-empty value as a config error (fails to start), so it can only be # added here temporarily (with a real value) when you need to register a new user, # then removed again — see README "Adding another Matrix user". volumes: - matrix_data:/var/lib/continuwuity networks: - web labels: - "traefik.enable=true" - "traefik.http.routers.matrix.rule=Host(`${MATRIX_SERVER_NAME}`)" - "traefik.http.routers.matrix.entrypoints=websecure" - "traefik.http.routers.matrix.tls.certresolver=letsencrypt" - "traefik.http.services.matrix.loadbalancer.server.port=8008" claude-agent: image: ${GITEA_REGISTRY_IMAGE} container_name: claude-agent restart: unless-stopped # Explicit vars, not env_file: .env — Portainer's git-based stack deploy clones the # repo fresh (no .env present, it's gitignored) and only performs ${VAR} substitution # from the stack's own Env list, so env_file here would silently fail to load anything. environment: GITEA_URL: ${GITEA_URL} GITEA_TOKEN: ${GITEA_TOKEN} GITEA_WEBHOOK_SECRET: ${GITEA_WEBHOOK_SECRET} ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY} MATRIX_HOMESERVER_URL: ${MATRIX_HOMESERVER_URL} MATRIX_BOT_TOKEN: ${MATRIX_BOT_TOKEN} MATRIX_CONTROL_ROOM_ID: ${MATRIX_CONTROL_ROOM_ID} volumes: - agent_workspace:/workspace networks: - web labels: - "traefik.enable=true" - "traefik.http.routers.agent.rule=Host(`${AGENT_HOSTNAME}`)" - "traefik.http.routers.agent.entrypoints=websecure" - "traefik.http.routers.agent.tls.certresolver=letsencrypt" - "traefik.http.services.agent.loadbalancer.server.port=3001" act_runner: image: gitea/act_runner:latest container_name: act_runner restart: unless-stopped environment: GITEA_INSTANCE_URL: ${GITEA_URL} GITEA_RUNNER_REGISTRATION_TOKEN: ${ACT_RUNNER_REGISTRATION_TOKEN} GITEA_RUNNER_NAME: gitops-vps-runner # catthehacker/ubuntu:act-latest is the standard job-container image for # act/act_runner — includes git + docker CLI, which job steps need. GITEA_RUNNER_LABELS: docker:docker://catthehacker/ubuntu:act-latest CONFIG_FILE: /etc/act_runner/config.yaml volumes: - /var/run/docker.sock:/var/run/docker.sock - act_runner_data:/data - ./act-runner-config.yaml:/etc/act_runner/config.yaml:ro networks: - web networks: web: external: true volumes: matrix_data: agent_workspace: act_runner_data: