# Control plane: ajs-accountd + dashboard, with Resin as an optional profile. # # cd deploy # cp .env.example .env # then fill in the placeholders # docker compose up -d --build # docker compose --profile resin up -d # # accountd and Resin use host networking because both must reach loopback # endpoints: Resin's admin API on 127.0.0.1:2260 and the local SOCKS5 nodes the # panel leases point at. The dashboard stays on a bridge network and publishes # to host loopback only. # # No secret is written here: values come from .env (gitignored), which is # optional at parse time so `docker compose config` works on a fresh checkout. name: ajs-control-plane services: accountd: build: context: .. dockerfile: daemon/Dockerfile image: ajs-accountd:local container_name: ajs-accountd network_mode: host env_file: - path: .env required: false environment: AJS_LISTEN: 127.0.0.1:2261 AJS_STATE_DIR: /data # Optional HTTPS admin listener (AJS-276). Configure it through .env: # AJS_TLS_CERT=/certs/fullchain.pem, AJS_TLS_KEY=/certs/privkey.pem and # optionally AJS_TLS_LISTEN (default 127.0.0.1:2262). The mount below # provides /certs; a partial configuration aborts startup. volumes: - accountd-data:/data # TLS material for the optional HTTPS listener. The directory may be # empty โ€” the daemon reads it only when AJS_TLS_CERT/AJS_TLS_KEY are # set โ€” and it is mounted read-only so the daemon cannot rewrite its own # certificate. Put *.pem files here: they are gitignored. - ./certs:/certs:ro restart: unless-stopped healthcheck: test: ["CMD", "wget", "-qO-", "http://127.0.0.1:2261/healthz"] interval: 30s timeout: 3s retries: 3 start_period: 10s dashboard: build: context: .. dockerfile: dashboard/Dockerfile args: # Baked into the bundle: rebuild the image after changing these. # The image refuses VITE_USE_MOCK=true; the mock console is `npm run dev`. VITE_USE_MOCK: ${VITE_USE_MOCK:-false} VITE_API_BASE: ${VITE_API_BASE:-http://127.0.0.1:2261} image: ajs-dashboard:local container_name: ajs-dashboard network_mode: bridge ports: - "127.0.0.1:18081:80" depends_on: accountd: condition: service_healthy restart: unless-stopped resin: profiles: ["resin"] image: ghcr.io/resinat/resin:1.3.0 container_name: ajs-resin network_mode: host # Least privilege (S6): this is a third-party image. It gets only its own # two credentials โ€” not the shared .env, which also holds AJS_MASTER_KEY # (the AES-256 key that decrypts every account password) and the panel # admin token. .env stays accountd/dashboard-only. environment: # Host networking means Resin would otherwise bind 0.0.0.0; keep the # admin API and SOCKS5 listener on loopback. RESIN_LISTEN_ADDRESS: ${RESIN_LISTEN_ADDRESS:-127.0.0.1} RESIN_PORT: ${RESIN_PORT:-2260} # Non-secret knobs from the documented minimum (docs/PANEL.md ยง7). The # previous env_file: .env passed whatever the operator set; these keep # that reachable without handing over AJS_MASTER_KEY or the panel token. # RESIN_PROXY_BYPASS lists target hosts dialed directly instead of # through a pool node; empty means Resin's own default (no bypass). RESIN_PROXY_BYPASS: ${RESIN_PROXY_BYPASS:-} # The volumes below mount these paths; change both together. RESIN_STATE_DIR: ${RESIN_STATE_DIR:-/var/lib/resin} RESIN_CACHE_DIR: ${RESIN_CACHE_DIR:-/var/cache/resin} RESIN_LOG_DIR: ${RESIN_LOG_DIR:-/var/log/resin} RESIN_ADMIN_TOKEN: ${RESIN_ADMIN_TOKEN:-} RESIN_PROXY_TOKEN: ${RESIN_PROXY_TOKEN:-} volumes: - resin-state:/var/lib/resin - resin-cache:/var/cache/resin - resin-log:/var/log/resin restart: unless-stopped volumes: accountd-data: resin-state: resin-cache: resin-log: