# Phonix — full stack (ASP.NET Core API + Next.js storefront) on a single host. # Config comes from a sibling .env file (copy .env.example → .env and edit before first run). # docker compose up -d --build services: backend: build: context: ./backend dockerfile: Dockerfile image: phonix-backend container_name: phonix-backend restart: unless-stopped environment: ASPNETCORE_ENVIRONMENT: Production ASPNETCORE_URLS: http://+:5228 # Public URL of the storefront — used for CORS, email links, and the verify/reset URLs. PHONIX_FRONTEND_URL: ${PHONIX_FRONTEND_URL:-http://localhost:3000} # ALL durable state lands under the mounted volume so it survives rebuilds/redeploys: the store, # the audit log, uploaded media (profile/product/site images + KYC docs), the session key ring, # and logs. Set explicitly so persistence never relies on the app's (per-release) base directory. PHONIX_DATA_FILE: /app/App_Data/store.json PHONIX_AUDIT_FILE: /app/App_Data/audit_store.json PHONIX_UPLOADS_DIR: /app/App_Data/ProtectedUploads PHONIX_KEYS_DIR: /app/App_Data/keys PHONIX_LOG_DIR: /app/App_Data/logs # Set both to "true" only when a TLS-terminating reverse proxy sits in front (see DEPLOY.md). PHONIX_BEHIND_PROXY: ${PHONIX_BEHIND_PROXY:-false} PHONIX_TRUSTED_PROXIES: ${PHONIX_TRUSTED_PROXIES:-127.0.0.1} PHONIX_FORCE_HTTPS: ${PHONIX_FORCE_HTTPS:-false} # (Re)applied on every boot: mints the first admin account if it doesn't exist yet, or fixes its # role/password if it does. Without these, a fresh deploy has NO way to reach the admin panel. PHONIX_OWNER_USERNAME: ${PHONIX_OWNER_USERNAME:-} PHONIX_OWNER_PASSWORD: ${PHONIX_OWNER_PASSWORD:-} # Encrypts store backups downloaded/sent from /admin/backup (AES-256-GCM). Optional — without it, # backup files are plain JSON/zip. Losing this key after the fact makes old encrypted backups # unrecoverable, so set it once and keep it somewhere safe outside the server. PHONIX_BACKUP_KEY: ${PHONIX_BACKUP_KEY:-} # Optional High Availability (one Primary + one Standby, business continuity only). Leave # PHONIX_CLUSTER_MODE unset/standalone for a normal single-server install — every var below is a # no-op in that case. scripts/install.sh prompts for these when clustering is chosen. PHONIX_CLUSTER_MODE: ${PHONIX_CLUSTER_MODE:-standalone} PHONIX_NODE_ID: ${PHONIX_NODE_ID:-} PHONIX_CLUSTER_PEER: ${PHONIX_CLUSTER_PEER:-} PHONIX_CLUSTER_SECRET: ${PHONIX_CLUSTER_SECRET:-} # PHONIX_CLUSTER_PEER MUST be an https:// URL — cluster sync carries orders/user data and the initial # snapshot, so the link is required to be encrypted. Set this to "true" ONLY for local/dev testing over # plain HTTP; the server refuses to start with an http:// peer otherwise. PHONIX_CLUSTER_ALLOW_INSECURE: ${PHONIX_CLUSTER_ALLOW_INSECURE:-} # How often a Standby reconciles uploaded media (KYC/avatars/product images) with the Primary. Default 60s. PHONIX_CLUSTER_MEDIA_SYNC_INTERVAL_SECONDS: ${PHONIX_CLUSTER_MEDIA_SYNC_INTERVAL_SECONDS:-} volumes: - phonix-data:/app/App_Data ports: - "${BACKEND_PORT:-5228}:5228" # Lets the container reach the host's Postfix (port 25) via host.docker.internal. extra_hosts: - "host.docker.internal:host-gateway" healthcheck: test: ["CMD", "curl", "-fsS", "http://localhost:5228/health"] interval: 30s timeout: 5s retries: 3 start_period: 20s frontend: build: context: ./frontend dockerfile: Dockerfile args: # Baked into the client bundle at build time — must be the PUBLIC API URL the browser reaches. NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:5228} image: phonix-frontend container_name: phonix-frontend restart: unless-stopped depends_on: backend: condition: service_healthy ports: - "${FRONTEND_PORT:-3000}:3000" healthcheck: test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000',r=>process.exit(r.statusCode<500?0:1)).on('error',()=>process.exit(1))"] interval: 30s timeout: 5s retries: 3 start_period: 30s volumes: phonix-data: