feat: add flexible docker-compose architecture with profiles
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
- Add OpenBao services to docker-compose.yml with profiles (openbao, full) - Add docker-compose.build.yml for local builds vs registry pulls - Make PostgreSQL and Valkey optional via profiles (database, cache) - Create example compose files for common deployment scenarios: - docker/docker-compose.example.turnkey.yml (all bundled) - docker/docker-compose.example.external.yml (all external) - docker/docker.example.hybrid.yml (mixed deployment) - Update documentation: - Enhance .env.example with profiles and external service examples - Update README.md with deployment mode quick starts - Add deployment scenarios to docs/OPENBAO.md - Create docker/DOCKER-COMPOSE-GUIDE.md with comprehensive guide - Clean up repository structure: - Move shell scripts to scripts/ directory - Move documentation to docs/ directory - Move docker compose examples to docker/ directory - Configure for external Authentik with internal services: - Comment out Authentik services (using external OIDC) - Comment out unused volumes for disabled services - Keep postgres, valkey, openbao as internal services This provides a flexible deployment architecture supporting turnkey, production (all external), and hybrid configurations via Docker Compose profiles. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
265
docker/DOCKER-COMPOSE-GUIDE.md
Normal file
265
docker/DOCKER-COMPOSE-GUIDE.md
Normal file
@@ -0,0 +1,265 @@
|
||||
# Docker Compose Guide
|
||||
|
||||
This project provides two Docker Compose configurations for different use cases.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Using Pre-Built Images (Recommended)
|
||||
|
||||
Pull and run the latest images from the Gitea container registry:
|
||||
|
||||
```bash
|
||||
# Copy environment template
|
||||
cp .env.example .env
|
||||
|
||||
# Edit .env and set IMAGE_TAG (optional, defaults to 'dev')
|
||||
# IMAGE_TAG=dev # Development images (develop branch)
|
||||
# IMAGE_TAG=latest # Production images (main branch)
|
||||
# IMAGE_TAG=658ec077 # Specific commit SHA
|
||||
|
||||
# Pull and start services
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
**File:** `docker-compose.yml`
|
||||
|
||||
### Building Locally
|
||||
|
||||
Build all images locally (useful for development):
|
||||
|
||||
```bash
|
||||
# Copy environment template
|
||||
cp .env.example .env
|
||||
|
||||
# Build and start services
|
||||
docker compose -f docker-compose.build.yml up -d --build
|
||||
```
|
||||
|
||||
**File:** `docker-compose.build.yml`
|
||||
|
||||
## Compose File Comparison
|
||||
|
||||
| File | Purpose | Image Source | When to Use |
|
||||
| -------------------------- | --------------------- | ------------------------------------------------- | ------------------------------------------------- |
|
||||
| `docker-compose.yml` | Pull pre-built images | `git.mosaicstack.dev/mosaic/stack-*:${IMAGE_TAG}` | Production, staging, testing with CI-built images |
|
||||
| `docker-compose.build.yml` | Build locally | Local Dockerfiles | Active development, testing local changes |
|
||||
|
||||
## Image Tags
|
||||
|
||||
The `IMAGE_TAG` environment variable controls which image version to pull:
|
||||
|
||||
- `dev` - Latest development build from `develop` branch (default)
|
||||
- `latest` - Latest stable build from `main` branch
|
||||
- `658ec077` - Specific commit SHA (first 8 characters)
|
||||
- `v1.0.0` - Specific version tag
|
||||
|
||||
## Services Included
|
||||
|
||||
Both compose files include the same services:
|
||||
|
||||
**Core Stack:**
|
||||
|
||||
- `postgres` - PostgreSQL 17 with pgvector extension
|
||||
- `valkey` - Redis-compatible cache
|
||||
- `api` - Mosaic NestJS API
|
||||
- `web` - Mosaic Next.js Web App
|
||||
- `orchestrator` - Mosaic Agent Orchestrator
|
||||
|
||||
**Optional Services (via profiles):**
|
||||
|
||||
- `authentik-*` - OIDC authentication provider (profile: `authentik`)
|
||||
- `ollama` - Local LLM service (profile: `ollama`)
|
||||
- `traefik` - Reverse proxy (profile: `traefik-bundled`)
|
||||
|
||||
## Switching Between Modes
|
||||
|
||||
### From Local Build to Registry Images
|
||||
|
||||
```bash
|
||||
# Stop current containers
|
||||
docker compose -f docker-compose.build.yml down
|
||||
|
||||
# Switch to registry images
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### From Registry Images to Local Build
|
||||
|
||||
```bash
|
||||
# Stop current containers
|
||||
docker compose down
|
||||
|
||||
# Switch to local build
|
||||
docker compose -f docker-compose.build.yml up -d --build
|
||||
```
|
||||
|
||||
## CI/CD Pipeline
|
||||
|
||||
The Woodpecker CI pipeline automatically builds and pushes images to `git.mosaicstack.dev`:
|
||||
|
||||
- **Develop branch** → `stack-*:dev` + `stack-*:<commit-sha>`
|
||||
- **Main branch** → `stack-*:latest` + `stack-*:<commit-sha>`
|
||||
- **Tags** → `stack-*:v1.0.0` + `stack-*:<commit-sha>`
|
||||
|
||||
## Docker Registry Authentication
|
||||
|
||||
To pull images from the private Gitea registry, you need to authenticate:
|
||||
|
||||
```bash
|
||||
# Login to Gitea registry
|
||||
docker login git.mosaicstack.dev
|
||||
|
||||
# Enter your Gitea username and password
|
||||
# Or use a Gitea access token as the password
|
||||
```
|
||||
|
||||
To generate a Gitea access token:
|
||||
|
||||
1. Go to https://git.mosaicstack.dev/user/settings/applications
|
||||
2. Create a new access token with `read:package` permission
|
||||
3. Use your username and the token as your password
|
||||
|
||||
## Updating Images
|
||||
|
||||
### Update to Latest Dev Images
|
||||
|
||||
```bash
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### Update to Specific Version
|
||||
|
||||
```bash
|
||||
# Set IMAGE_TAG in .env
|
||||
echo "IMAGE_TAG=658ec077" >> .env
|
||||
|
||||
# Pull and restart
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Image not found" errors
|
||||
|
||||
**Cause:** Registry authentication required or image doesn't exist
|
||||
|
||||
**Fix:**
|
||||
|
||||
```bash
|
||||
# Login to registry
|
||||
docker login git.mosaicstack.dev
|
||||
|
||||
# Verify image exists
|
||||
docker search git.mosaicstack.dev/mosaic/stack-api
|
||||
|
||||
# Check available tags at:
|
||||
# https://git.mosaicstack.dev/mosaic/-/packages
|
||||
```
|
||||
|
||||
### Build failures with docker-compose.build.yml
|
||||
|
||||
**Cause:** Missing dependencies or build context
|
||||
|
||||
**Fix:**
|
||||
|
||||
```bash
|
||||
# Ensure you're in the project root
|
||||
cd /path/to/mosaic-stack
|
||||
|
||||
# Install dependencies first
|
||||
pnpm install
|
||||
|
||||
# Build with verbose output
|
||||
docker compose -f docker-compose.build.yml build --progress=plain
|
||||
```
|
||||
|
||||
### Services fail to start
|
||||
|
||||
**Cause:** Environment variables not set
|
||||
|
||||
**Fix:**
|
||||
|
||||
```bash
|
||||
# Copy environment template
|
||||
cp .env.example .env
|
||||
|
||||
# Edit .env and replace all REPLACE_WITH_* placeholders
|
||||
# Minimum required:
|
||||
# - POSTGRES_PASSWORD
|
||||
# - JWT_SECRET
|
||||
# - BETTER_AUTH_SECRET
|
||||
# - ENCRYPTION_KEY
|
||||
|
||||
# Restart services
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## Example Configurations
|
||||
|
||||
The repository includes three example compose files for common deployment scenarios:
|
||||
|
||||
### Turnkey (All Bundled)
|
||||
|
||||
**File:** `docker/docker-compose.example.turnkey.yml`
|
||||
**Use Case:** Local development, testing, demo environments
|
||||
|
||||
```bash
|
||||
# Set in .env
|
||||
COMPOSE_PROFILES=full
|
||||
IMAGE_TAG=dev
|
||||
|
||||
# Start all services
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
All services run in Docker: PostgreSQL, Valkey, OpenBao, Authentik, Ollama.
|
||||
|
||||
### Production (All External)
|
||||
|
||||
**File:** `docker/docker-compose.example.external.yml`
|
||||
**Use Case:** Production with managed services (AWS, GCP, Azure)
|
||||
|
||||
```bash
|
||||
# Copy example file
|
||||
cp docker/docker-compose.example.external.yml docker-compose.override.yml
|
||||
|
||||
# Edit .env with external service URLs
|
||||
# COMPOSE_PROFILES= # Empty
|
||||
# DATABASE_URL=postgresql://...
|
||||
# VALKEY_URL=redis://...
|
||||
# etc.
|
||||
|
||||
# Start only API and Web
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Uses external managed services for all infrastructure.
|
||||
|
||||
### Hybrid (Mixed)
|
||||
|
||||
**File:** `docker/docker-compose.example.hybrid.yml`
|
||||
**Use Case:** Staging environments, gradual migration
|
||||
|
||||
```bash
|
||||
# Copy example file
|
||||
cp docker/docker-compose.example.hybrid.yml docker-compose.override.yml
|
||||
|
||||
# Set in .env
|
||||
COMPOSE_PROFILES=database,cache,ollama
|
||||
# ... external service URLs for auth/secrets
|
||||
|
||||
# Start mixed deployment
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Bundles database/cache/AI locally, uses external auth/secrets.
|
||||
|
||||
## See Also
|
||||
|
||||
- [Deployment Guide](docs/DOCKER.md) - Full Docker deployment documentation
|
||||
- [Configuration Guide](docs/CONFIGURATION.md) - Environment variable reference
|
||||
- [CI/CD Pipeline](.woodpecker.yml) - Automated build and deployment
|
||||
584
docker/docker-compose.build.yml
Normal file
584
docker/docker-compose.build.yml
Normal file
@@ -0,0 +1,584 @@
|
||||
services:
|
||||
# ======================
|
||||
# PostgreSQL Database
|
||||
# ======================
|
||||
postgres:
|
||||
build:
|
||||
context: ./docker/postgres
|
||||
dockerfile: Dockerfile
|
||||
container_name: mosaic-postgres
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: ${POSTGRES_USER:-mosaic}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-mosaic_dev_password}
|
||||
POSTGRES_DB: ${POSTGRES_DB:-mosaic}
|
||||
# Performance tuning
|
||||
POSTGRES_SHARED_BUFFERS: ${POSTGRES_SHARED_BUFFERS:-256MB}
|
||||
POSTGRES_EFFECTIVE_CACHE_SIZE: ${POSTGRES_EFFECTIVE_CACHE_SIZE:-1GB}
|
||||
POSTGRES_MAX_CONNECTIONS: ${POSTGRES_MAX_CONNECTIONS:-100}
|
||||
ports:
|
||||
- "${POSTGRES_PORT:-5432}:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
- ./docker/postgres/init-scripts:/docker-entrypoint-initdb.d:ro
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-mosaic} -d ${POSTGRES_DB:-mosaic}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
networks:
|
||||
- mosaic-internal
|
||||
profiles:
|
||||
- database
|
||||
- full
|
||||
labels:
|
||||
- "com.mosaic.service=database"
|
||||
- "com.mosaic.description=PostgreSQL 17 with pgvector"
|
||||
|
||||
# ======================
|
||||
# Valkey Cache
|
||||
# ======================
|
||||
valkey:
|
||||
image: valkey/valkey:8-alpine
|
||||
container_name: mosaic-valkey
|
||||
restart: unless-stopped
|
||||
command:
|
||||
- valkey-server
|
||||
- --maxmemory ${VALKEY_MAXMEMORY:-256mb}
|
||||
- --maxmemory-policy allkeys-lru
|
||||
- --appendonly yes
|
||||
ports:
|
||||
- "${VALKEY_PORT:-6379}:6379"
|
||||
volumes:
|
||||
- valkey_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "valkey-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
networks:
|
||||
- mosaic-internal
|
||||
profiles:
|
||||
- cache
|
||||
- full
|
||||
labels:
|
||||
- "com.mosaic.service=cache"
|
||||
- "com.mosaic.description=Valkey Redis-compatible cache"
|
||||
|
||||
# ======================
|
||||
# Authentik PostgreSQL
|
||||
# ======================
|
||||
authentik-postgres:
|
||||
image: postgres:17-alpine
|
||||
container_name: mosaic-authentik-postgres
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: ${AUTHENTIK_POSTGRES_USER:-authentik}
|
||||
POSTGRES_PASSWORD: ${AUTHENTIK_POSTGRES_PASSWORD:-authentik_password}
|
||||
POSTGRES_DB: ${AUTHENTIK_POSTGRES_DB:-authentik}
|
||||
volumes:
|
||||
- authentik_postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${AUTHENTIK_POSTGRES_USER:-authentik}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 20s
|
||||
networks:
|
||||
- mosaic-internal
|
||||
profiles:
|
||||
- authentik
|
||||
- full
|
||||
labels:
|
||||
- "com.mosaic.service=auth-database"
|
||||
- "com.mosaic.description=Authentik PostgreSQL database"
|
||||
|
||||
# ======================
|
||||
# Authentik Redis
|
||||
# ======================
|
||||
authentik-redis:
|
||||
image: valkey/valkey:8-alpine
|
||||
container_name: mosaic-authentik-redis
|
||||
restart: unless-stopped
|
||||
command: valkey-server --save 60 1 --loglevel warning
|
||||
volumes:
|
||||
- authentik_redis_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "valkey-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
networks:
|
||||
- mosaic-internal
|
||||
profiles:
|
||||
- authentik
|
||||
- full
|
||||
labels:
|
||||
- "com.mosaic.service=auth-cache"
|
||||
- "com.mosaic.description=Authentik Redis cache"
|
||||
|
||||
# ======================
|
||||
# Authentik Server
|
||||
# ======================
|
||||
authentik-server:
|
||||
image: ghcr.io/goauthentik/server:2024.12.1
|
||||
container_name: mosaic-authentik-server
|
||||
restart: unless-stopped
|
||||
command: server
|
||||
environment:
|
||||
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:-change-this-to-a-random-secret}
|
||||
AUTHENTIK_ERROR_REPORTING__ENABLED: ${AUTHENTIK_ERROR_REPORTING:-false}
|
||||
AUTHENTIK_POSTGRESQL__HOST: authentik-postgres
|
||||
AUTHENTIK_POSTGRESQL__PORT: 5432
|
||||
AUTHENTIK_POSTGRESQL__NAME: ${AUTHENTIK_POSTGRES_DB:-authentik}
|
||||
AUTHENTIK_POSTGRESQL__USER: ${AUTHENTIK_POSTGRES_USER:-authentik}
|
||||
AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_POSTGRES_PASSWORD:-authentik_password}
|
||||
AUTHENTIK_REDIS__HOST: authentik-redis
|
||||
AUTHENTIK_REDIS__PORT: 6379
|
||||
AUTHENTIK_BOOTSTRAP_PASSWORD: ${AUTHENTIK_BOOTSTRAP_PASSWORD:-admin}
|
||||
AUTHENTIK_BOOTSTRAP_EMAIL: ${AUTHENTIK_BOOTSTRAP_EMAIL:-admin@localhost}
|
||||
AUTHENTIK_COOKIE_DOMAIN: ${AUTHENTIK_COOKIE_DOMAIN:-.localhost}
|
||||
ports:
|
||||
- "${AUTHENTIK_PORT_HTTP:-9000}:9000"
|
||||
- "${AUTHENTIK_PORT_HTTPS:-9443}:9443"
|
||||
volumes:
|
||||
- authentik_media:/media
|
||||
- authentik_templates:/templates
|
||||
depends_on:
|
||||
authentik-postgres:
|
||||
condition: service_healthy
|
||||
authentik-redis:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD",
|
||||
"wget",
|
||||
"--no-verbose",
|
||||
"--tries=1",
|
||||
"--spider",
|
||||
"http://localhost:9000/-/health/live/",
|
||||
]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 90s
|
||||
networks:
|
||||
- mosaic-internal
|
||||
- mosaic-public
|
||||
profiles:
|
||||
- authentik
|
||||
- full
|
||||
labels:
|
||||
- "com.mosaic.service=auth-server"
|
||||
- "com.mosaic.description=Authentik OIDC server"
|
||||
# Traefik labels (activated when TRAEFIK_MODE=bundled or upstream)
|
||||
- "traefik.enable=${TRAEFIK_ENABLE:-false}"
|
||||
- "traefik.http.routers.mosaic-auth.rule=Host(`${MOSAIC_AUTH_DOMAIN:-auth.mosaic.local}`)"
|
||||
- "traefik.http.routers.mosaic-auth.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}"
|
||||
- "traefik.http.routers.mosaic-auth.tls=${TRAEFIK_TLS_ENABLED:-true}"
|
||||
- "traefik.http.services.mosaic-auth.loadbalancer.server.port=9000"
|
||||
- "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-mosaic-public}"
|
||||
# Let's Encrypt (if enabled)
|
||||
- "traefik.http.routers.mosaic-auth.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}"
|
||||
|
||||
# ======================
|
||||
# Authentik Worker
|
||||
# ======================
|
||||
authentik-worker:
|
||||
image: ghcr.io/goauthentik/server:2024.12.1
|
||||
container_name: mosaic-authentik-worker
|
||||
restart: unless-stopped
|
||||
command: worker
|
||||
environment:
|
||||
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:-change-this-to-a-random-secret}
|
||||
AUTHENTIK_ERROR_REPORTING__ENABLED: ${AUTHENTIK_ERROR_REPORTING:-false}
|
||||
AUTHENTIK_POSTGRESQL__HOST: authentik-postgres
|
||||
AUTHENTIK_POSTGRESQL__PORT: 5432
|
||||
AUTHENTIK_POSTGRESQL__NAME: ${AUTHENTIK_POSTGRES_DB:-authentik}
|
||||
AUTHENTIK_POSTGRESQL__USER: ${AUTHENTIK_POSTGRES_USER:-authentik}
|
||||
AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_POSTGRES_PASSWORD:-authentik_password}
|
||||
AUTHENTIK_REDIS__HOST: authentik-redis
|
||||
AUTHENTIK_REDIS__PORT: 6379
|
||||
volumes:
|
||||
- authentik_media:/media
|
||||
- authentik_certs:/certs
|
||||
- authentik_templates:/templates
|
||||
depends_on:
|
||||
authentik-postgres:
|
||||
condition: service_healthy
|
||||
authentik-redis:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- mosaic-internal
|
||||
profiles:
|
||||
- authentik
|
||||
- full
|
||||
labels:
|
||||
- "com.mosaic.service=auth-worker"
|
||||
- "com.mosaic.description=Authentik background worker"
|
||||
|
||||
# ======================
|
||||
# Ollama (Optional AI Service)
|
||||
# ======================
|
||||
ollama:
|
||||
image: ollama/ollama:latest
|
||||
container_name: mosaic-ollama
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${OLLAMA_PORT:-11434}:11434"
|
||||
volumes:
|
||||
- ollama_data:/root/.ollama
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:11434/api/tags"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 60s
|
||||
networks:
|
||||
- mosaic-internal
|
||||
profiles:
|
||||
- ollama
|
||||
- full
|
||||
labels:
|
||||
- "com.mosaic.service=ai"
|
||||
- "com.mosaic.description=Ollama LLM service"
|
||||
# Uncomment if you have GPU support
|
||||
# deploy:
|
||||
# resources:
|
||||
# reservations:
|
||||
# devices:
|
||||
# - driver: nvidia
|
||||
# count: 1
|
||||
# capabilities: [gpu]
|
||||
|
||||
# ======================
|
||||
# OpenBao Secrets Management (Optional)
|
||||
# ======================
|
||||
openbao:
|
||||
build:
|
||||
context: ./docker/openbao
|
||||
dockerfile: Dockerfile
|
||||
container_name: mosaic-openbao
|
||||
restart: unless-stopped
|
||||
user: root
|
||||
ports:
|
||||
- "127.0.0.1:${OPENBAO_PORT:-8200}:8200"
|
||||
volumes:
|
||||
- openbao_data:/openbao/data
|
||||
- openbao_init:/openbao/init
|
||||
environment:
|
||||
VAULT_ADDR: http://0.0.0.0:8200
|
||||
SKIP_SETCAP: "true"
|
||||
command: ["bao", "server", "-config=/openbao/config/config.hcl"]
|
||||
cap_add:
|
||||
- IPC_LOCK
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "nc -z 127.0.0.1 8200 || exit 1"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
networks:
|
||||
- mosaic-internal
|
||||
profiles:
|
||||
- openbao
|
||||
- full
|
||||
labels:
|
||||
- "com.mosaic.service=secrets"
|
||||
- "com.mosaic.description=OpenBao secrets management"
|
||||
|
||||
openbao-init:
|
||||
build:
|
||||
context: ./docker/openbao
|
||||
dockerfile: Dockerfile
|
||||
container_name: mosaic-openbao-init
|
||||
restart: unless-stopped
|
||||
user: root
|
||||
volumes:
|
||||
- openbao_init:/openbao/init
|
||||
environment:
|
||||
VAULT_ADDR: http://openbao:8200
|
||||
command: ["/openbao/init.sh"]
|
||||
depends_on:
|
||||
openbao:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- mosaic-internal
|
||||
profiles:
|
||||
- openbao
|
||||
- full
|
||||
labels:
|
||||
- "com.mosaic.service=secrets-init"
|
||||
- "com.mosaic.description=OpenBao auto-initialization sidecar"
|
||||
|
||||
# ======================
|
||||
# Traefik Reverse Proxy (Optional - Bundled Mode)
|
||||
# ======================
|
||||
# Enable with: COMPOSE_PROFILES=traefik-bundled or --profile traefik-bundled
|
||||
# Set TRAEFIK_MODE=bundled in .env
|
||||
traefik:
|
||||
image: traefik:v3.2
|
||||
container_name: mosaic-traefik
|
||||
restart: unless-stopped
|
||||
command:
|
||||
- "--configFile=/etc/traefik/traefik.yml"
|
||||
ports:
|
||||
- "${TRAEFIK_HTTP_PORT:-80}:80"
|
||||
- "${TRAEFIK_HTTPS_PORT:-443}:443"
|
||||
- "${TRAEFIK_DASHBOARD_PORT:-8080}:8080"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ./docker/traefik/traefik.yml:/etc/traefik/traefik.yml:ro
|
||||
- ./docker/traefik/dynamic:/etc/traefik/dynamic:ro
|
||||
- traefik_letsencrypt:/letsencrypt
|
||||
environment:
|
||||
- TRAEFIK_ACME_EMAIL=${TRAEFIK_ACME_EMAIL:-}
|
||||
networks:
|
||||
- mosaic-public
|
||||
profiles:
|
||||
- traefik-bundled
|
||||
- full
|
||||
labels:
|
||||
- "com.mosaic.service=reverse-proxy"
|
||||
- "com.mosaic.description=Traefik reverse proxy and load balancer"
|
||||
healthcheck:
|
||||
test: ["CMD", "traefik", "healthcheck", "--ping"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 20s
|
||||
|
||||
# ======================
|
||||
# Mosaic API
|
||||
# ======================
|
||||
api:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./apps/api/Dockerfile
|
||||
args:
|
||||
- NODE_ENV=production
|
||||
container_name: mosaic-api
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
# API Configuration - PORT is what NestJS reads
|
||||
PORT: ${API_PORT:-3001}
|
||||
API_HOST: ${API_HOST:-0.0.0.0}
|
||||
# Database
|
||||
DATABASE_URL: postgresql://${POSTGRES_USER:-mosaic}:${POSTGRES_PASSWORD:-mosaic_dev_password}@postgres:5432/${POSTGRES_DB:-mosaic}
|
||||
# Cache
|
||||
VALKEY_URL: redis://valkey:6379
|
||||
# Authentication
|
||||
OIDC_ISSUER: ${OIDC_ISSUER}
|
||||
OIDC_CLIENT_ID: ${OIDC_CLIENT_ID}
|
||||
OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET}
|
||||
OIDC_REDIRECT_URI: ${OIDC_REDIRECT_URI:-http://localhost:3001/auth/callback}
|
||||
# JWT
|
||||
JWT_SECRET: ${JWT_SECRET:-change-this-to-a-random-secret}
|
||||
JWT_EXPIRATION: ${JWT_EXPIRATION:-24h}
|
||||
# Ollama (optional)
|
||||
OLLAMA_ENDPOINT: ${OLLAMA_ENDPOINT:-http://ollama:11434}
|
||||
# OpenBao (optional)
|
||||
OPENBAO_ADDR: ${OPENBAO_ADDR:-http://openbao:8200}
|
||||
volumes:
|
||||
- openbao_init:/openbao/init:ro
|
||||
ports:
|
||||
- "${API_PORT:-3001}:${API_PORT:-3001}"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
valkey:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD-SHELL",
|
||||
'node -e "require(''http'').get(''http://localhost:${API_PORT:-3001}/health'', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"',
|
||||
]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
networks:
|
||||
- mosaic-internal
|
||||
- mosaic-public
|
||||
labels:
|
||||
- "com.mosaic.service=api"
|
||||
- "com.mosaic.description=Mosaic NestJS API"
|
||||
# Traefik labels (activated when TRAEFIK_MODE=bundled or upstream)
|
||||
- "traefik.enable=${TRAEFIK_ENABLE:-false}"
|
||||
- "traefik.http.routers.mosaic-api.rule=Host(`${MOSAIC_API_DOMAIN:-api.mosaic.local}`)"
|
||||
- "traefik.http.routers.mosaic-api.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}"
|
||||
- "traefik.http.routers.mosaic-api.tls=${TRAEFIK_TLS_ENABLED:-true}"
|
||||
- "traefik.http.services.mosaic-api.loadbalancer.server.port=${API_PORT:-3001}"
|
||||
- "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-mosaic-public}"
|
||||
# Let's Encrypt (if enabled)
|
||||
- "traefik.http.routers.mosaic-api.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}"
|
||||
|
||||
# ======================
|
||||
# Mosaic Orchestrator
|
||||
# ======================
|
||||
orchestrator:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./apps/orchestrator/Dockerfile
|
||||
container_name: mosaic-orchestrator
|
||||
restart: unless-stopped
|
||||
# Run as non-root user (node:node, UID 1000)
|
||||
user: "1000:1000"
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
# Orchestrator Configuration
|
||||
ORCHESTRATOR_PORT: 3001
|
||||
# Valkey
|
||||
VALKEY_URL: redis://valkey:6379
|
||||
# Claude API
|
||||
CLAUDE_API_KEY: ${CLAUDE_API_KEY}
|
||||
# Docker
|
||||
DOCKER_SOCKET: /var/run/docker.sock
|
||||
# Git
|
||||
GIT_USER_NAME: "Mosaic Orchestrator"
|
||||
GIT_USER_EMAIL: "orchestrator@mosaicstack.dev"
|
||||
# Security
|
||||
KILLSWITCH_ENABLED: true
|
||||
SANDBOX_ENABLED: true
|
||||
ports:
|
||||
- "3002:3001"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- orchestrator_workspace:/workspace
|
||||
depends_on:
|
||||
valkey:
|
||||
condition: service_healthy
|
||||
api:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test:
|
||||
["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:3001/health || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
networks:
|
||||
- mosaic-internal
|
||||
# Security hardening
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- NET_BIND_SERVICE
|
||||
read_only: false # Cannot be read-only due to workspace writes
|
||||
tmpfs:
|
||||
- /tmp:noexec,nosuid,size=100m
|
||||
labels:
|
||||
- "com.mosaic.service=orchestrator"
|
||||
- "com.mosaic.description=Mosaic Agent Orchestrator"
|
||||
- "com.mosaic.security=hardened"
|
||||
- "com.mosaic.security.non-root=true"
|
||||
- "com.mosaic.security.capabilities=minimal"
|
||||
|
||||
# ======================
|
||||
# Mosaic Web
|
||||
# ======================
|
||||
web:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./apps/web/Dockerfile
|
||||
args:
|
||||
- NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL:-http://localhost:3001}
|
||||
container_name: mosaic-web
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
PORT: ${WEB_PORT:-3000}
|
||||
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:3001}
|
||||
ports:
|
||||
- "${WEB_PORT:-3000}:${WEB_PORT:-3000}"
|
||||
depends_on:
|
||||
api:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD-SHELL",
|
||||
'node -e "require(''http'').get(''http://localhost:${WEB_PORT:-3000}'', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"',
|
||||
]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
networks:
|
||||
- mosaic-public
|
||||
labels:
|
||||
- "com.mosaic.service=web"
|
||||
- "com.mosaic.description=Mosaic Next.js Web App"
|
||||
# Traefik labels (activated when TRAEFIK_MODE=bundled or upstream)
|
||||
- "traefik.enable=${TRAEFIK_ENABLE:-false}"
|
||||
- "traefik.http.routers.mosaic-web.rule=Host(`${MOSAIC_WEB_DOMAIN:-mosaic.local}`)"
|
||||
- "traefik.http.routers.mosaic-web.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}"
|
||||
- "traefik.http.routers.mosaic-web.tls=${TRAEFIK_TLS_ENABLED:-true}"
|
||||
- "traefik.http.services.mosaic-web.loadbalancer.server.port=${WEB_PORT:-3000}"
|
||||
- "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-mosaic-public}"
|
||||
# Let's Encrypt (if enabled)
|
||||
- "traefik.http.routers.mosaic-web.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}"
|
||||
|
||||
# ======================
|
||||
# Volumes
|
||||
# ======================
|
||||
volumes:
|
||||
postgres_data:
|
||||
name: mosaic-postgres-data
|
||||
driver: local
|
||||
valkey_data:
|
||||
name: mosaic-valkey-data
|
||||
driver: local
|
||||
authentik_postgres_data:
|
||||
name: mosaic-authentik-postgres-data
|
||||
driver: local
|
||||
authentik_redis_data:
|
||||
name: mosaic-authentik-redis-data
|
||||
driver: local
|
||||
authentik_media:
|
||||
name: mosaic-authentik-media
|
||||
driver: local
|
||||
authentik_certs:
|
||||
name: mosaic-authentik-certs
|
||||
driver: local
|
||||
authentik_templates:
|
||||
name: mosaic-authentik-templates
|
||||
driver: local
|
||||
ollama_data:
|
||||
name: mosaic-ollama-data
|
||||
driver: local
|
||||
openbao_data:
|
||||
name: mosaic-openbao-data
|
||||
driver: local
|
||||
openbao_init:
|
||||
name: mosaic-openbao-init
|
||||
driver: local
|
||||
traefik_letsencrypt:
|
||||
name: mosaic-traefik-letsencrypt
|
||||
driver: local
|
||||
orchestrator_workspace:
|
||||
name: mosaic-orchestrator-workspace
|
||||
driver: local
|
||||
|
||||
# ======================
|
||||
# Networks
|
||||
# ======================
|
||||
networks:
|
||||
# Internal network for database/cache isolation
|
||||
# Note: NOT marked as 'internal: true' because API needs external access
|
||||
# for Authentik OIDC and external Ollama services
|
||||
mosaic-internal:
|
||||
name: mosaic-internal
|
||||
driver: bridge
|
||||
# Public network for services that need external access
|
||||
mosaic-public:
|
||||
name: mosaic-public
|
||||
driver: bridge
|
||||
122
docker/docker-compose.example.external.yml
Normal file
122
docker/docker-compose.example.external.yml
Normal file
@@ -0,0 +1,122 @@
|
||||
# ==============================================
|
||||
# Mosaic Stack - External Services Deployment Example
|
||||
# ==============================================
|
||||
# This example shows a production deployment using external managed services.
|
||||
# All infrastructure (database, cache, secrets, auth, AI) is managed externally.
|
||||
#
|
||||
# Usage:
|
||||
# 1. Copy this file to docker-compose.override.yml
|
||||
# 2. Set COMPOSE_PROFILES= (empty) in .env
|
||||
# 3. Configure external service URLs in .env (see below)
|
||||
# 4. Run: docker compose up -d
|
||||
#
|
||||
# Or run directly:
|
||||
# docker compose -f docker-compose.yml -f docker-compose.example.external.yml up -d
|
||||
#
|
||||
# Services Included:
|
||||
# - API (NestJS) - configured to use external services
|
||||
# - Web (Next.js)
|
||||
# - Orchestrator (Agent management)
|
||||
#
|
||||
# External Services (configured via .env):
|
||||
# - PostgreSQL (e.g., AWS RDS, Google Cloud SQL, Azure Database)
|
||||
# - Redis/Valkey (e.g., AWS ElastiCache, Google Memorystore, Azure Cache)
|
||||
# - OpenBao/Vault (e.g., HashiCorp Vault Cloud, self-hosted)
|
||||
# - OIDC Provider (e.g., Auth0, Okta, Google, Azure AD)
|
||||
# - LLM Service (e.g., hosted Ollama, OpenAI, Anthropic)
|
||||
#
|
||||
# Required Environment Variables (.env):
|
||||
# COMPOSE_PROFILES= # Empty - no bundled services
|
||||
# IMAGE_TAG=latest
|
||||
#
|
||||
# # External Database
|
||||
# DATABASE_URL=postgresql://user:password@rds.example.com:5432/mosaic
|
||||
#
|
||||
# # External Cache
|
||||
# VALKEY_URL=redis://elasticache.example.com:6379
|
||||
#
|
||||
# # External Secrets (OpenBao/Vault)
|
||||
# OPENBAO_ADDR=https://vault.example.com:8200
|
||||
# OPENBAO_ROLE_ID=your-role-id
|
||||
# OPENBAO_SECRET_ID=your-secret-id
|
||||
#
|
||||
# # External OIDC Authentication
|
||||
# OIDC_ENABLED=true
|
||||
# OIDC_ISSUER=https://auth.example.com/
|
||||
# OIDC_CLIENT_ID=your-client-id
|
||||
# OIDC_CLIENT_SECRET=your-client-secret
|
||||
#
|
||||
# # External LLM Service
|
||||
# OLLAMA_ENDPOINT=https://ollama.example.com:11434
|
||||
# # Or use OpenAI:
|
||||
# # AI_PROVIDER=openai
|
||||
# # OPENAI_API_KEY=sk-...
|
||||
#
|
||||
# ==============================================
|
||||
|
||||
services:
|
||||
# Disable all bundled infrastructure services
|
||||
postgres:
|
||||
profiles:
|
||||
- disabled
|
||||
|
||||
valkey:
|
||||
profiles:
|
||||
- disabled
|
||||
|
||||
openbao:
|
||||
profiles:
|
||||
- disabled
|
||||
|
||||
openbao-init:
|
||||
profiles:
|
||||
- disabled
|
||||
|
||||
authentik-postgres:
|
||||
profiles:
|
||||
- disabled
|
||||
|
||||
authentik-redis:
|
||||
profiles:
|
||||
- disabled
|
||||
|
||||
authentik-server:
|
||||
profiles:
|
||||
- disabled
|
||||
|
||||
authentik-worker:
|
||||
profiles:
|
||||
- disabled
|
||||
|
||||
ollama:
|
||||
profiles:
|
||||
- disabled
|
||||
|
||||
# Configure API to use external services
|
||||
api:
|
||||
environment:
|
||||
# External database (e.g., AWS RDS)
|
||||
DATABASE_URL: ${DATABASE_URL}
|
||||
|
||||
# External cache (e.g., AWS ElastiCache)
|
||||
VALKEY_URL: ${VALKEY_URL}
|
||||
|
||||
# External secrets (e.g., HashiCorp Vault Cloud)
|
||||
OPENBAO_ADDR: ${OPENBAO_ADDR}
|
||||
OPENBAO_ROLE_ID: ${OPENBAO_ROLE_ID}
|
||||
OPENBAO_SECRET_ID: ${OPENBAO_SECRET_ID}
|
||||
|
||||
# External LLM (e.g., hosted Ollama or OpenAI)
|
||||
OLLAMA_ENDPOINT: ${OLLAMA_ENDPOINT}
|
||||
|
||||
# External OIDC (e.g., Auth0, Okta, Google)
|
||||
OIDC_ENABLED: ${OIDC_ENABLED}
|
||||
OIDC_ISSUER: ${OIDC_ISSUER}
|
||||
OIDC_CLIENT_ID: ${OIDC_CLIENT_ID}
|
||||
OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET}
|
||||
|
||||
# Web app remains unchanged
|
||||
# web: (uses defaults from docker-compose.yml)
|
||||
|
||||
# Orchestrator remains unchanged
|
||||
# orchestrator: (uses defaults from docker-compose.yml)
|
||||
110
docker/docker-compose.example.hybrid.yml
Normal file
110
docker/docker-compose.example.hybrid.yml
Normal file
@@ -0,0 +1,110 @@
|
||||
# ==============================================
|
||||
# Mosaic Stack - Hybrid Deployment Example
|
||||
# ==============================================
|
||||
# This example shows a hybrid deployment mixing bundled and external services.
|
||||
# Common for staging environments: bundled database/cache, external auth/secrets.
|
||||
#
|
||||
# Usage:
|
||||
# 1. Copy this file to docker-compose.override.yml
|
||||
# 2. Set COMPOSE_PROFILES=database,cache,ollama in .env
|
||||
# 3. Configure external service URLs in .env (see below)
|
||||
# 4. Run: docker compose up -d
|
||||
#
|
||||
# Or run directly:
|
||||
# docker compose -f docker-compose.yml -f docker-compose.example.hybrid.yml up -d
|
||||
#
|
||||
# Services Included (Bundled):
|
||||
# - PostgreSQL 17 with pgvector
|
||||
# - Valkey (Redis-compatible cache)
|
||||
# - Ollama (local LLM)
|
||||
# - API (NestJS)
|
||||
# - Web (Next.js)
|
||||
# - Orchestrator (Agent management)
|
||||
#
|
||||
# Services Included (External):
|
||||
# - OpenBao/Vault (managed secrets)
|
||||
# - Authentik/OIDC (managed authentication)
|
||||
#
|
||||
# Environment Variables (.env):
|
||||
# COMPOSE_PROFILES=database,cache,ollama # Enable only these bundled services
|
||||
# IMAGE_TAG=dev
|
||||
#
|
||||
# # Bundled Database (default from docker-compose.yml)
|
||||
# DATABASE_URL=postgresql://mosaic:${POSTGRES_PASSWORD}@postgres:5432/mosaic
|
||||
#
|
||||
# # Bundled Cache (default from docker-compose.yml)
|
||||
# VALKEY_URL=redis://valkey:6379
|
||||
#
|
||||
# # Bundled Ollama (default from docker-compose.yml)
|
||||
# OLLAMA_ENDPOINT=http://ollama:11434
|
||||
#
|
||||
# # External Secrets (OpenBao/Vault)
|
||||
# OPENBAO_ADDR=https://vault.example.com:8200
|
||||
# OPENBAO_ROLE_ID=your-role-id
|
||||
# OPENBAO_SECRET_ID=your-secret-id
|
||||
#
|
||||
# # External OIDC Authentication
|
||||
# OIDC_ENABLED=true
|
||||
# OIDC_ISSUER=https://auth.example.com/
|
||||
# OIDC_CLIENT_ID=your-client-id
|
||||
# OIDC_CLIENT_SECRET=your-client-secret
|
||||
#
|
||||
# ==============================================
|
||||
|
||||
services:
|
||||
# Use bundled PostgreSQL and Valkey (enabled via database,cache profiles)
|
||||
# No overrides needed - profiles handle this
|
||||
|
||||
# Disable bundled Authentik - use external OIDC
|
||||
authentik-postgres:
|
||||
profiles:
|
||||
- disabled
|
||||
|
||||
authentik-redis:
|
||||
profiles:
|
||||
- disabled
|
||||
|
||||
authentik-server:
|
||||
profiles:
|
||||
- disabled
|
||||
|
||||
authentik-worker:
|
||||
profiles:
|
||||
- disabled
|
||||
|
||||
# Disable bundled OpenBao - use external vault
|
||||
openbao:
|
||||
profiles:
|
||||
- disabled
|
||||
|
||||
openbao-init:
|
||||
profiles:
|
||||
- disabled
|
||||
|
||||
# Use bundled Ollama (enabled via ollama profile)
|
||||
# No override needed
|
||||
|
||||
# Configure API for hybrid deployment
|
||||
api:
|
||||
environment:
|
||||
# Bundled database (default)
|
||||
DATABASE_URL: postgresql://${POSTGRES_USER:-mosaic}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-mosaic}
|
||||
|
||||
# Bundled cache (default)
|
||||
VALKEY_URL: redis://valkey:6379
|
||||
|
||||
# External secrets
|
||||
OPENBAO_ADDR: ${OPENBAO_ADDR}
|
||||
OPENBAO_ROLE_ID: ${OPENBAO_ROLE_ID}
|
||||
OPENBAO_SECRET_ID: ${OPENBAO_SECRET_ID}
|
||||
|
||||
# Bundled Ollama (default)
|
||||
OLLAMA_ENDPOINT: http://ollama:11434
|
||||
|
||||
# External OIDC
|
||||
OIDC_ENABLED: ${OIDC_ENABLED}
|
||||
OIDC_ISSUER: ${OIDC_ISSUER}
|
||||
OIDC_CLIENT_ID: ${OIDC_CLIENT_ID}
|
||||
OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET}
|
||||
|
||||
# Web and Orchestrator use defaults from docker-compose.yml
|
||||
43
docker/docker-compose.example.turnkey.yml
Normal file
43
docker/docker-compose.example.turnkey.yml
Normal file
@@ -0,0 +1,43 @@
|
||||
# ==============================================
|
||||
# Mosaic Stack - Turnkey Deployment Example
|
||||
# ==============================================
|
||||
# This example shows a complete all-in-one deployment with all services bundled.
|
||||
# Ideal for local development, testing, and demo environments.
|
||||
#
|
||||
# Usage:
|
||||
# 1. Copy this file to docker-compose.override.yml (optional)
|
||||
# 2. Set COMPOSE_PROFILES=full in .env
|
||||
# 3. Run: docker compose up -d
|
||||
#
|
||||
# Or run directly:
|
||||
# docker compose -f docker-compose.yml -f docker-compose.example.turnkey.yml up -d
|
||||
#
|
||||
# Services Included:
|
||||
# - PostgreSQL 17 with pgvector
|
||||
# - Valkey (Redis-compatible cache)
|
||||
# - OpenBao (secrets management)
|
||||
# - Authentik (OIDC authentication)
|
||||
# - Ollama (local LLM)
|
||||
# - Traefik (reverse proxy) - optional, requires traefik-bundled profile
|
||||
# - API (NestJS)
|
||||
# - Web (Next.js)
|
||||
# - Orchestrator (Agent management)
|
||||
#
|
||||
# Environment Variables (.env):
|
||||
# COMPOSE_PROFILES=full
|
||||
# IMAGE_TAG=dev # or latest
|
||||
#
|
||||
# All services run in Docker containers with no external dependencies.
|
||||
# ==============================================
|
||||
|
||||
services:
|
||||
# No service overrides needed - the main docker-compose.yml handles everything
|
||||
# This file serves as documentation for turnkey deployment
|
||||
# Set COMPOSE_PROFILES=full in your .env file to enable all services
|
||||
|
||||
# Placeholder to make the file valid YAML
|
||||
# (Docker Compose requires at least one service definition)
|
||||
_placeholder:
|
||||
image: alpine:latest
|
||||
profiles:
|
||||
- never-used
|
||||
Reference in New Issue
Block a user