# fullstackhero — production docker-compose. # Run from this directory: # cp .env.example .env && $EDITOR .env # docker compose up -d --build # # Operator owns the edge (TLS / subdomain routing). This file publishes # only the FSH services on host ports; the data plane (postgres/redis/ # minio) stays compose-internal unless you uncomment their ports blocks. name: fsh services: postgres: image: postgres:18-alpine container_name: fsh-postgres restart: unless-stopped environment: POSTGRES_DB: fsh POSTGRES_USER: fsh POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required} volumes: - pg_data:/var/lib/postgresql/data - ./postgres-init:/docker-entrypoint-initdb.d:ro healthcheck: test: ["CMD-SHELL", "pg_isready -U fsh -d fsh"] interval: 5s timeout: 3s retries: 12 # Uncomment to expose for host psql / pg_dump access: # ports: # - "5432:5432" redis: image: valkey/valkey:9.1.0-alpine container_name: fsh-redis restart: unless-stopped command: - valkey-server - --requirepass - ${REDIS_PASSWORD:?REDIS_PASSWORD is required} - --appendonly - "yes" volumes: - redis_data:/data healthcheck: test: ["CMD-SHELL", "valkey-cli -a $$REDIS_PASSWORD PING | grep PONG"] interval: 5s timeout: 3s retries: 12 environment: REDIS_PASSWORD: ${REDIS_PASSWORD} # Uncomment to expose for host redis-cli: # ports: # - "6379:6379" minio: image: minio/minio:latest container_name: fsh-minio restart: unless-stopped command: ["server", "/data", "--console-address", ":9001"] environment: MINIO_ROOT_USER: ${MINIO_ROOT_USER:?MINIO_ROOT_USER is required} MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:?MINIO_ROOT_PASSWORD is required} volumes: - minio_data:/data healthcheck: test: ["CMD", "mc", "ready", "local"] interval: 10s timeout: 3s retries: 12 # Uncomment to expose the console (admin UI) + S3 API to the host: # ports: # - "9000:9000" # S3 API # - "9001:9001" # Web console # One-shot: create the bucket the Files module writes to. The app never # auto-creates it (S3StorageService just PutObjects into Storage:S3:Bucket), # so without this the first upload fails with NoSuchBucket. No anonymous # policy is set — objects are served via the API / presigned URLs, not a # public bucket. minio-init: image: minio/mc:latest container_name: fsh-minio-init restart: "no" depends_on: minio: { condition: service_healthy } environment: MINIO_ROOT_USER: ${MINIO_ROOT_USER} MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD} entrypoint: /bin/sh command: - -c - | mc alias set local http://minio:9000 "$$MINIO_ROOT_USER" "$$MINIO_ROOT_PASSWORD" mc mb --ignore-existing local/fsh migrator: build: context: ../.. dockerfile: src/Host/FSH.Starter.DbMigrator/Dockerfile image: fsh/dbmigrator:local container_name: fsh-migrator restart: "no" depends_on: postgres: { condition: service_healthy } environment: DOTNET_ENVIRONMENT: Production DatabaseOptions__ConnectionString: Host=postgres;Database=fsh;Username=fsh;Password=${POSTGRES_PASSWORD} CachingOptions__Redis: redis,password=${REDIS_PASSWORD} JwtOptions__SigningKey: ${JWT_SIGNING_KEY:?JWT_SIGNING_KEY is required} Seed__DefaultAdminPassword: ${SEED_ADMIN_PASSWORD:?SEED_ADMIN_PASSWORD is required} command: ["apply", "--seed"] api: build: context: ../.. dockerfile: src/Host/FSH.Starter.Api/Dockerfile image: fsh/api:local container_name: fsh-api restart: unless-stopped depends_on: postgres: { condition: service_healthy } redis: { condition: service_healthy } minio: { condition: service_healthy } minio-init: { condition: service_completed_successfully } migrator: { condition: service_completed_successfully } environment: DOTNET_ENVIRONMENT: Production ASPNETCORE_URLS: http://+:8080 DatabaseOptions__ConnectionString: Host=postgres;Database=fsh;Username=fsh;Password=${POSTGRES_PASSWORD} CachingOptions__Redis: redis,password=${REDIS_PASSWORD} JwtOptions__SigningKey: ${JWT_SIGNING_KEY} Seed__DefaultAdminPassword: ${SEED_ADMIN_PASSWORD} Storage__Provider: s3 Storage__S3__ServiceUrl: http://minio:9000 Storage__S3__AccessKey: ${MINIO_ROOT_USER} Storage__S3__SecretKey: ${MINIO_ROOT_PASSWORD} Storage__S3__Bucket: fsh Storage__S3__ForcePathStyle: "true" AllowedHosts: "*" HangfireOptions__UserName: ${HANGFIRE_USERNAME:?HANGFIRE_USERNAME is required} HangfireOptions__Password: ${HANGFIRE_PASSWORD:?HANGFIRE_PASSWORD is required} CorsOptions__AllowedOrigins__0: ${FSH_ADMIN_URL:?FSH_ADMIN_URL is required} CorsOptions__AllowedOrigins__1: ${FSH_DASHBOARD_URL:?FSH_DASHBOARD_URL is required} OpenTelemetryOptions__Exporter__Otlp__Endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT:-} ports: - "${FSH_API_PORT:-8080}:8080" admin: build: context: ../../clients/admin image: fsh/admin:local container_name: fsh-admin restart: unless-stopped depends_on: migrator: { condition: service_completed_successfully } environment: FSH_API_URL: ${FSH_API_URL:?FSH_API_URL is required} FSH_DASHBOARD_URL: ${FSH_DASHBOARD_URL} FSH_DEFAULT_TENANT: ${FSH_DEFAULT_TENANT:-root} ports: - "${FSH_ADMIN_PORT:-8081}:80" dashboard: build: context: ../../clients/dashboard image: fsh/dashboard:local container_name: fsh-dashboard restart: unless-stopped depends_on: migrator: { condition: service_completed_successfully } environment: FSH_API_URL: ${FSH_API_URL} FSH_DEFAULT_TENANT: ${FSH_DEFAULT_TENANT:-root} ports: - "${FSH_DASHBOARD_PORT:-8082}:80" volumes: pg_data: redis_data: minio_data: