version: "3.8" # kept so the legacy docker-compose 1.x still reads the file; Compose v2 ignores it with a warning services: actual-sync: build: context: . dockerfile: Dockerfile image: actual-sync:latest container_name: actual-sync restart: unless-stopped # Environment variables (optional, overrides config.json) environment: # PUID/PGID: run as this UID/GID and chown the data/logs volumes to match. # Defaults to 1001:1001. On Unraid set 99:100 (nobody:users). - PUID=1001 - PGID=1001 - TZ=Europe/Madrid - NODE_ENV=production # Mount config and data volumes volumes: # Configuration file - ./config/config.json:/app/config/config.json:ro # Persistent data directories - actual-sync-data:/app/data - actual-sync-logs:/app/logs # If using custom data directories for budget files # - ./budget-data:/budget-data # Dashboard, health and metrics port, published on this machine only: # nothing else on the network can reach it. To use the dashboard from # another machine, put a reverse proxy in front (docs/DOCKER_DEPLOYMENT.md, # "Behind a reverse proxy"), or change this to "3000:3000" and set # healthCheck.dashboard.auth (and healthCheck.auth for the metrics) in # config.json first. healthCheck.host stays "0.0.0.0" inside the container # either way; this line decides who can reach it. ports: - "127.0.0.1:3000:3000" # Container hardening (#253). Every Linux capability is dropped, then the # three the entrypoint needs are added back. It runs as root only to apply # PUID/PGID and chown the volumes, then drops to actualuser, which runs # with no capability at all. Verified by a container run against each # setting (see docs/DOCKER_DEPLOYMENT.md, "Container hardening"). # CHOWN: chown -R of /app/data and /app/logs # SETUID, SETGID: usermod/groupmod for PUID/PGID, and su-exec to the user # A bind-mounted data or logs directory that another user owns with mode # 700 also needs DAC_READ_SEARCH, or the chown cannot enter it. # Alternative: set `user: "1001:1001"` (the owner of your directories). # The entrypoint then skips its root phase and needs no capability, so # cap_add can be removed entirely. cap_drop: - ALL cap_add: - CHOWN - SETUID - SETGID # No process in the container can gain privileges through a setuid binary, # whatever capabilities it holds. security_opt: - no-new-privileges:true # Optional: read-only root filesystem. It works with the default PUID/PGID # (1001:1001). Any other value needs usermod and groupmod to write # /etc/passwd and /etc/group, which a read-only root refuses, so the # container does not start. Uncomment both lines together. # read_only: true # tmpfs: # - /tmp # Resource limits deploy: resources: limits: cpus: '1.0' memory: 512M reservations: cpus: '0.25' memory: 128M # Health check healthcheck: test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/health', (r) => { process.exit(r.statusCode === 200 ? 0 : 1); }).on('error', () => process.exit(1));"] interval: 30s timeout: 10s retries: 3 start_period: 40s # Logging configuration (#451). stdout is the main log sink and the Docker # log driver keeps it; json-file has no size limit by default, so cap it here. logging: driver: "json-file" options: max-size: "10m" max-file: "5" # Network configuration networks: - actual-network # Optional: Actual Budget server # Uncomment if you want to run Actual Budget alongside sync service # actual-server: # image: actualbudget/actual-server:latest # container_name: actual-server # restart: unless-stopped # ports: # - "5006:5006" # volumes: # - actual-server-data:/data # networks: # - actual-network # Optional: Monitoring with Prometheus # prometheus: # image: prom/prometheus:latest # container_name: prometheus # restart: unless-stopped # ports: # - "9090:9090" # volumes: # - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro # - prometheus-data:/prometheus # command: # - '--config.file=/etc/prometheus/prometheus.yml' # - '--storage.tsdb.path=/prometheus' # networks: # - actual-network # Optional: Grafana for visualization # grafana: # image: grafana/grafana:latest # container_name: grafana # restart: unless-stopped # ports: # - "3001:3000" # volumes: # - grafana-data:/var/lib/grafana # environment: # - GF_SECURITY_ADMIN_PASSWORD=admin # networks: # - actual-network networks: actual-network: driver: bridge volumes: actual-sync-data: driver: local actual-sync-logs: driver: local # actual-server-data: # driver: local # prometheus-data: # driver: local # grafana-data: # driver: local