From 66269fa8166aec84c55cfca46d27c258f51fe24b Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Sun, 8 Feb 2026 17:41:11 -0600 Subject: [PATCH] feat(portainer): add Portainer-optimized deployment files - Create docker-compose.portainer.yml - No env_file directive (Portainer doesn't support it) - Port exposed on 0.0.0.0 (Portainer limitation) - Simple depends_on syntax - All environment variables explicit - Create docs/PORTAINER-DEPLOYMENT.md - Complete Portainer deployment guide - Step-by-step instructions - Environment variables reference - Troubleshooting section - Best practices for security and backups - Update README.md - Add Portainer deployment section - Reference Portainer deployment guide Fixes: - 'open /data/compose/94/.env: no such file or directory' - 'ignoring IP-address (127.0.0.1:8200:8200/tcp)' warning Portainer requires different compose syntax than standard docker-compose. This provides a deployment path optimized for Portainer's stack parser. Co-Authored-By: Claude Opus 4.6 --- README.md | 23 +++ docker-compose.portainer.yml | 95 +++++++++ docs/PORTAINER-DEPLOYMENT.md | 380 +++++++++++++++++++++++++++++++++++ 3 files changed, 498 insertions(+) create mode 100644 docker-compose.portainer.yml create mode 100644 docs/PORTAINER-DEPLOYMENT.md diff --git a/README.md b/README.md index f08d2e1..36eddcf 100644 --- a/README.md +++ b/README.md @@ -208,6 +208,29 @@ docker stack ps mosaic See [Docker Swarm Deployment Guide](docs/SWARM-DEPLOYMENT.md) and [Quick Reference](docs/SWARM-QUICKREF.md) for complete documentation. +### Portainer Deployment + +**Recommended for GUI-based stack management.** + +Portainer provides a web UI for managing Docker containers and stacks. Use the Portainer-optimized compose file: + +**File:** `docker-compose.portainer.yml` + +**Key differences from standard compose:** + +- No `env_file` directive (define variables in Portainer UI) +- Port exposed on all interfaces (Portainer limitation) +- Optimized for Portainer's stack parser + +**Quick Steps:** + +1. Create `mosaic_internal` overlay network in Portainer +2. Deploy `mosaic-openbao` stack with `docker-compose.portainer.yml` +3. Deploy `mosaic` swarm stack with `docker-compose.swarm.yml` +4. Configure environment variables in Portainer UI + +See [Portainer Deployment Guide](docs/PORTAINER-DEPLOYMENT.md) for detailed instructions. + ## Project Structure ``` diff --git a/docker-compose.portainer.yml b/docker-compose.portainer.yml new file mode 100644 index 0000000..d15af76 --- /dev/null +++ b/docker-compose.portainer.yml @@ -0,0 +1,95 @@ +# ============================================== +# OpenBao Standalone Deployment - Portainer Version +# ============================================== +# +# This file is optimized for Portainer deployment: +# - No env_file directive (define variables in Portainer's environment editor) +# - Port exposed on all interfaces (Portainer limitation) +# - All environment variables explicitly defined +# +# Usage in Portainer: +# 1. Stacks -> Add Stack +# 2. Name: mosaic-openbao +# 3. Paste this file content +# 4. Add environment variables in "Environment variables" section: +# - IMAGE_TAG=dev +# - OPENBAO_PORT=8200 +# 5. Deploy +# +# SECURITY NOTE: Port 8200 will be exposed on 0.0.0.0 (all interfaces) +# Use firewall rules to restrict access if needed. +# ============================================== + +services: + # ====================== + # OpenBao Secrets Vault + # ====================== + openbao: + image: git.mosaicstack.dev/mosaic/stack-openbao:${IMAGE_TAG:-dev} + container_name: mosaic-openbao + command: server -config=/openbao/config/config.hcl + environment: + OPENBAO_ADDR: http://0.0.0.0:8200 + ports: + - "${OPENBAO_PORT:-8200}:8200" + volumes: + - openbao_data:/openbao/data + - openbao_logs:/openbao/logs + - openbao_init:/openbao/init + cap_add: + - IPC_LOCK + healthcheck: + test: + - CMD + - wget + - --spider + - --quiet + - http://localhost:8200/v1/sys/health?standbyok=true + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + restart: unless-stopped + networks: + - mosaic_internal + + # ====================== + # OpenBao Init Sidecar + # ====================== + # Auto-initializes and unseals OpenBao on first run + openbao-init: + image: git.mosaicstack.dev/mosaic/stack-openbao:${IMAGE_TAG:-dev} + container_name: mosaic-openbao-init + command: /openbao/init.sh + environment: + OPENBAO_ADDR: http://openbao:8200 + volumes: + - openbao_init:/openbao/init + depends_on: + - openbao + restart: "no" + networks: + - mosaic_internal + +# ====================== +# Volumes +# ====================== +volumes: + openbao_data: + name: mosaic-openbao-data + driver: local + openbao_logs: + name: mosaic-openbao-logs + driver: local + openbao_init: + name: mosaic-openbao-init + driver: local + +# ====================== +# Networks +# ====================== +# Connect to the swarm stack's internal network +networks: + mosaic_internal: + external: true + name: mosaic_internal diff --git a/docs/PORTAINER-DEPLOYMENT.md b/docs/PORTAINER-DEPLOYMENT.md new file mode 100644 index 0000000..3015964 --- /dev/null +++ b/docs/PORTAINER-DEPLOYMENT.md @@ -0,0 +1,380 @@ +# Portainer Deployment Guide + +## Overview + +This guide covers deploying Mosaic Stack via Portainer. Portainer has specific requirements that differ from standard docker-compose deployments. + +## Key Differences + +**Portainer Limitations:** + +- ❌ Does NOT support `env_file:` directive +- ❌ Does NOT support localhost-only port bindings (`127.0.0.1:port`) +- ❌ Does NOT support health check conditions in `depends_on` +- ✅ DOES support environment variables defined in Portainer UI +- ✅ DOES support external networks + +**Solution:** Use `docker-compose.portainer.yml` which is optimized for Portainer. + +## Prerequisites + +### 1. Create Overlay Network + +**In Portainer:** + +1. Go to **Networks** → **Add network** +2. Name: `mosaic_internal` +3. Driver: `overlay` +4. Enable **Attachable** +5. Click **Create network** + +**Or via CLI:** + +```bash +docker network create --driver=overlay --attachable mosaic_internal +``` + +### 2. Registry Authentication + +If using private registry images from `git.mosaicstack.dev`: + +**In Portainer:** + +1. Go to **Registries** → **Add registry** +2. Type: **Custom Registry** +3. Name: `Gitea Mosaic` +4. Registry URL: `git.mosaicstack.dev` +5. Username: Your Gitea username +6. Password: Your Gitea password or access token +7. Click **Add registry** + +## Deployment Steps + +### Step 1: Deploy OpenBao Stack + +**Why First?** OpenBao must be running before the main stack starts, as the API needs to connect to it. + +**In Portainer:** + +1. Go to **Stacks** → **Add stack** +2. **Name:** `mosaic-openbao` +3. **Build method:** Web editor +4. **Web editor:** Copy and paste contents of `docker-compose.portainer.yml` +5. **Environment variables:** + ``` + IMAGE_TAG=dev + OPENBAO_PORT=8200 + ``` +6. Click **Deploy the stack** + +**Verify Deployment:** + +1. Go to **Stacks** → `mosaic-openbao` +2. Check both containers are running: + - `mosaic-openbao` (should be running) + - `mosaic-openbao-init` (will stop after initialization) +3. View logs: + - Click on `mosaic-openbao-init` → **Logs** + - Look for: `✓ OpenBao initialized successfully` + +**Wait 30 seconds** for initialization to complete before proceeding. + +### Step 2: Deploy Swarm Stack + +**In Portainer:** + +1. Go to **Stacks** → **Add stack** +2. **Name:** `mosaic` +3. **Build method:** Repository (recommended) or Web editor + +**Option A: Git Repository (Recommended)** + +- Repository URL: `https://git.mosaicstack.dev/mosaic/stack` +- Repository reference: `refs/heads/develop` +- Compose path: `docker-compose.swarm.yml` +- Authentication: Enable if repository is private +- Enable **Automatic updates** (optional) +- Update interval: `5m` (checks every 5 minutes) + +**Option B: Web Editor** + +- Copy and paste contents of `docker-compose.swarm.yml` + +4. **Environment variables:** + + ``` + IMAGE_TAG=dev + POSTGRES_PASSWORD= + JWT_SECRET= + BETTER_AUTH_SECRET= + ENCRYPTION_KEY= + OIDC_CLIENT_ID= + OIDC_CLIENT_SECRET= + OIDC_ISSUER=https://auth.diversecanvas.com/application/o/mosaic-stack/ + OIDC_REDIRECT_URI=https://api.mosaicstack.dev/auth/callback/authentik + OLLAMA_ENDPOINT=http://10.1.1.42:11434 + ``` + +5. Click **Deploy the stack** + +### Step 3: Verify Deployment + +**Check Services:** + +1. Go to **Swarm** → **Services** +2. Verify all services show `1/1` replicas: + - `mosaic_postgres` + - `mosaic_valkey` + - `mosaic_api` + - `mosaic_web` + - `mosaic_orchestrator` + +**Check Logs:** + +1. Go to **Swarm** → **Services** → `mosaic_api` +2. Click on the running task +3. Click **Logs** +4. Look for: `OpenBao connection established` or `Using fallback encryption` + +**Access Services:** + +- Web UI: http://app.mosaicstack.dev +- API: http://api.mosaicstack.dev/health +- OpenBao: http://:8200/ui + +## Environment Variables Reference + +### Required for All Deployments + +```bash +# Image Configuration +IMAGE_TAG=dev # or 'latest' or specific commit SHA + +# Database +POSTGRES_PASSWORD= +DATABASE_URL=postgresql://mosaic:${POSTGRES_PASSWORD}@postgres:5432/mosaic + +# Security +JWT_SECRET= # openssl rand -base64 32 +BETTER_AUTH_SECRET= +ENCRYPTION_KEY=<64-char-hex> # openssl rand -hex 32 + +# External OIDC (Authentik) +OIDC_CLIENT_ID= +OIDC_CLIENT_SECRET= +OIDC_ISSUER=https://auth.diversecanvas.com/application/o/mosaic-stack/ +OIDC_REDIRECT_URI=https://api.mosaicstack.dev/auth/callback/authentik + +# External Ollama +OLLAMA_ENDPOINT=http://10.1.1.42:11434 +``` + +### Optional Variables + +```bash +# Ports +OPENBAO_PORT=8200 +API_PORT=3001 +WEB_PORT=3000 + +# Cache +VALKEY_MAXMEMORY=256mb + +# Logging +LOG_LEVEL=info +DEBUG=false +``` + +## Updating Stacks + +### Update OpenBao + +1. Go to **Stacks** → `mosaic-openbao` +2. Click **Editor** +3. Update `IMAGE_TAG` environment variable (or compose file) +4. Click **Update the stack** +5. Enable **Re-pull image** +6. Click **Update** + +### Update Swarm Stack + +**If using Git Repository:** + +- Portainer automatically pulls updates based on update interval +- Or manually: **Stacks** → `mosaic` → **Pull and redeploy** + +**If using Web Editor:** + +1. Go to **Stacks** → `mosaic` +2. Click **Editor** +3. Update compose file or environment variables +4. Click **Update the stack** +5. Enable **Re-pull images** +6. Click **Update** + +## Troubleshooting + +### Stack Deployment Failed: "network not found" + +**Cause:** `mosaic_internal` network doesn't exist + +**Fix:** + +```bash +docker network create --driver=overlay --attachable mosaic_internal +``` + +### OpenBao Unhealthy + +**Check logs:** + +1. **Containers** → `mosaic-openbao` → **Logs** +2. Look for errors + +**Common issues:** + +- Port 8200 already in use → Stop conflicting service +- Config file not found → Rebuild image +- Permission denied → Check volume permissions + +### API Can't Connect to OpenBao + +**Symptoms:** + +- API logs show "connection refused" to OpenBao +- API falls back to encryption key + +**Check:** + +1. **Containers** → `mosaic-openbao` → Verify it's running +2. **Networks** → `mosaic_internal` → Verify both stacks are connected +3. API environment: `OPENBAO_ADDR=http://openbao:8200` + +**Test connectivity:** + +1. **Containers** → Find API container → **Console** +2. Run: `wget -qO- http://openbao:8200/v1/sys/health` + +### Services Not Starting in Swarm Stack + +**Check service logs:** + +1. **Swarm** → **Services** → Click service +2. Click running/failed task +3. View logs + +**Common issues:** + +- Missing environment variables +- Image pull failed (check registry authentication) +- Dependency not ready (database, OpenBao) + +## Best Practices + +### Security + +1. **Never expose OpenBao to public internet** without TLS +2. **Use firewall rules** to restrict port 8200 access +3. **Rotate secrets regularly** (JWT_SECRET, API keys) +4. **Use strong passwords** for PostgreSQL +5. **Enable TLS** for production deployments + +### Monitoring + +**In Portainer:** + +1. **Dashboard** → View resource usage +2. **Stacks** → Monitor stack health +3. **Swarm** → **Services** → Monitor replicas + +**Set up alerts:** + +1. **Notifications** → Configure webhook/email +2. **Events** → Enable service alerts + +### Backups + +**OpenBao Volumes:** + +```bash +# Backup +docker run --rm \ + -v mosaic-openbao-data:/data \ + -v $(pwd):/backup \ + alpine tar czf /backup/openbao-$(date +%Y%m%d).tar.gz -C /data . + +# Restore +docker run --rm \ + -v mosaic-openbao-data:/data \ + -v $(pwd):/backup \ + alpine sh -c 'rm -rf /data/* && tar xzf /backup/openbao-20260208.tar.gz -C /data' +``` + +**PostgreSQL:** + +```bash +# Backup +docker exec mosaic_postgres pg_dump -U mosaic mosaic > backup.sql + +# Restore +docker exec -i mosaic_postgres psql -U mosaic mosaic < backup.sql +``` + +## Advanced Configuration + +### Using External Services + +To use external PostgreSQL, Valkey, or Vault: + +1. Edit `docker-compose.swarm.yml` in Portainer +2. Comment out the service you want to replace +3. Update environment variables to point to external service +4. Update the stack + +**Example - External PostgreSQL:** + +```yaml +# Comment out postgres service +# postgres: +# ... + +# Update API environment +services: + api: + environment: + DATABASE_URL: postgresql://user:pass@external-db.example.com:5432/mosaic +``` + +### Custom Domains + +Update environment variables: + +```bash +NEXT_PUBLIC_APP_URL=https://mosaic.example.com +NEXT_PUBLIC_API_URL=https://api.example.com +OIDC_REDIRECT_URI=https://api.example.com/auth/callback/authentik +``` + +### Resource Limits + +Add to services in Portainer editor: + +```yaml +services: + api: + deploy: + resources: + limits: + cpus: "1" + memory: 1G + reservations: + cpus: "0.5" + memory: 512M +``` + +## See Also + +- [Swarm Deployment Guide](SWARM-DEPLOYMENT.md) - CLI-based deployment +- [OpenBao Deployment Guide](OPENBAO-DEPLOYMENT.md) - OpenBao options +- [Configuration Guide](CONFIGURATION.md) - All environment variables +- [Docker Compose Guide](../docker/DOCKER-COMPOSE-GUIDE.md) - Standalone deployment