name: vps-control services: postgres: image: postgres:16-alpine restart: unless-stopped environment: POSTGRES_DB: ${POSTGRES_DB:-vps_control} POSTGRES_USER: ${POSTGRES_USER:-vps_control} POSTGRES_PASSWORD_FILE: /run/secrets/db_password secrets: - db_password volumes: - postgres-data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] interval: 10s timeout: 5s retries: 8 networks: [internal] mem_limit: 512m cpus: 1.0 pids_limit: 200 security_opt: ["no-new-privileges:true"] backend: build: context: ./backend restart: unless-stopped environment: LISTEN_ADDRESS: :8080 DB_HOST: postgres DB_PORT: "5432" DB_NAME: ${POSTGRES_DB:-vps_control} DB_USER: ${POSTGRES_USER:-vps_control} DB_PASSWORD_FILE: /run/secrets/db_password DB_SSLMODE: disable COOKIE_SECURE: ${COOKIE_SECURE:-true} TRUST_PROXY: "true" SESSION_TTL: ${SESSION_TTL:-12h} METRICS_RETENTION: ${METRICS_RETENTION:-720h} LOGS_RETENTION: ${LOGS_RETENTION:-168h} AGENT_CREDENTIALS_DIR: /run/secrets INTEGRATION_TOKEN_FILE: /run/secrets/integration_token INITIAL_ADMIN_USERNAME: ${INITIAL_ADMIN_USERNAME:-admin} INITIAL_ADMIN_PASSWORD_FILE: /run/secrets/admin_password BOOTSTRAP_SERVER_NAME: ${BOOTSTRAP_SERVER_NAME:-} BOOTSTRAP_SERVER_ENDPOINT: ${BOOTSTRAP_SERVER_ENDPOINT:-} BOOTSTRAP_AGENT_CREDENTIAL_REF: ${BOOTSTRAP_AGENT_CREDENTIAL_REF:-agent_token} secrets: - db_password - admin_password - agent_token - integration_token depends_on: postgres: condition: service_healthy healthcheck: test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:8080/healthz"] interval: 10s timeout: 3s retries: 5 start_period: 10s networks: [internal, agent] read_only: true tmpfs: [/tmp] mem_limit: 128m cpus: 0.5 pids_limit: 100 security_opt: ["no-new-privileges:true"] frontend: build: context: ./frontend restart: unless-stopped ports: - "${HTTP_BIND_ADDRESS:-127.0.0.1}:${HTTP_PORT:-8080}:8080" depends_on: backend: condition: service_healthy healthcheck: test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:8080/"] interval: 10s timeout: 3s retries: 5 networks: [edge, internal] read_only: true tmpfs: - /tmp - /var/cache/nginx - /var/run mem_limit: 64m cpus: 0.25 pids_limit: 50 security_opt: ["no-new-privileges:true"] grafana: profiles: ["observability"] build: context: ./grafana restart: unless-stopped ports: - "${GRAFANA_BIND_ADDRESS:-127.0.0.1}:${GRAFANA_PORT:-3000}:3000" environment: GF_SERVER_ROOT_URL: ${GRAFANA_ROOT_URL:-http://127.0.0.1:3000} GF_USERS_ALLOW_SIGN_UP: "false" GF_AUTH_ANONYMOUS_ENABLED: "false" GF_SECURITY_COOKIE_SECURE: ${GRAFANA_COOKIE_SECURE:-false} GF_ANALYTICS_REPORTING_ENABLED: "false" GF_ANALYTICS_CHECK_FOR_UPDATES: "false" GF_LOG_LEVEL: warn secrets: - integration_token - grafana_admin_password - grafana_secret_key depends_on: backend: condition: service_healthy healthcheck: test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1:3000/api/health"] interval: 10s timeout: 5s retries: 12 start_period: 20s networks: [edge, internal] read_only: true tmpfs: - /tmp - /var/log/grafana volumes: - grafana-data:/var/lib/grafana - grafana-plugins:/usr/share/grafana/data/plugins-bundled mem_limit: 384m cpus: 0.75 pids_limit: 150 security_opt: ["no-new-privileges:true"] volumes: postgres-data: grafana-data: grafana-plugins: networks: edge: internal: internal: true agent: secrets: db_password: file: ${DB_PASSWORD_SECRET_FILE:-./secrets/db_password} admin_password: file: ${ADMIN_PASSWORD_SECRET_FILE:-./secrets/admin_password} agent_token: file: ${AGENT_TOKEN_SECRET_FILE:-./secrets/agent_token} integration_token: file: ${INTEGRATION_TOKEN_SECRET_FILE:-./secrets/integration_token} grafana_admin_password: file: ${GRAFANA_ADMIN_PASSWORD_SECRET_FILE:-./secrets/grafana_admin_password} grafana_secret_key: file: ${GRAFANA_SECRET_KEY_SECRET_FILE:-./secrets/grafana_secret_key}