| _support-docs | ||
| dashboards | ||
| mkdocs | ||
| notes-backend@829ce6d128 | ||
| portal | ||
| travel-app@db9ae32534 | ||
| tududi | ||
| vikunja | ||
| .env.global | ||
| .gitignore | ||
| README.md | ||
🐳 Bazinga Docker Stacks — Deployment Guide
Owner: Michelle · Version: 0.1 · Last Updated: 2026-09-26
This document is a prompt for agents to deploy new Docker service stacks into the Bazinga network and configure external HTTPS access through Traefik.
Repository Info: This repo is automatically synced to Forgejo (
ssh://git@ourbazinga.space/bazinga-admin/bbs-stacks.git) at 3:00 AM daily viaauto-git-sync.sh.
Quick Start
- Choose a stack name — lowercase, hyphen-separated, unique (e.g.,
my-service) - Create the stack directory —
mkdir -p /home/docker/stacks/{stackname}/{config,data} - Copy template files — use the templates below and customize for your service
- Register Traefik hostname — add to
.env.globaland Traefik config - Deploy — run
docker compose up -dfrom the stack directory - Verify — test direct port and HTTPS domain access
Prerequisites
- Docker and Docker Compose installed on the target machine
- SSH access to the machine (for Billie-based Traefik routing)
- Domain name or subdomain under
ourbazinga.space(DNS already configured) - Network
bazinga-netmust exist (docker network create bazinga-netif not present) - Traefik stack running on Billie (192.168.50.230:443 for HTTPS routing)
Directory Structure
/home/docker/stacks/
├── .env.global # Master configuration (all stacks inherit)
├── .env.secrets.master # Sensitive values (gitignored)
├── server-traefik/ # Traefik router (Billie only)
│ └── config/dynamic/ # Dynamic routing configs (*.yaml files)
├── server-postgres/ # PostgreSQL database (shared)
├── server-redis/ # Redis cache (shared)
│
└── my-service/ # ← Your new stack
├── .env # Service-specific env vars
├── docker-compose.yml # Service definition
├── config/ # Config files (mounted as volumes)
├── data/ # Persistent data (DB, files, cache)
└── [app-specific files] # mkdocs.yml, nginx.conf, etc.
Step 1: Create Environment File (.env)
Every stack has a .env file that sources global settings and defines service-specific overrides.
# ==============================================================================
# SERVICE: my-service
# Description: [Brief description of what this service does]
# External Port: [PORT] (e.g., 30100)
# Internal Port: [PORT] (what the app listens on inside the container)
# External URL : my-service.ourbazinga.space
# Variables come from .env and .env.global
# ==============================================================================
# Global config (inherited from .env.global, override here if needed)
DOMAIN=ourbazinga.space
NETWORK=bazinga-net
NETWORKS_EXTERNAL=true
TZ=Europe/London
RESTART=unless-stopped
# Resource limits
CPUS=0.50
MEMORY=512M
PUID=1000
PGID=1000
DOCKER_GID=999
# Database access (if using postgres)
POSTGRES_HOST=server-postgres
POSTGRES_PORT=5432
POSTGRES_USER=root
POSTGRES_PASSWORD=root_password
# Redis access (if using cache)
REDIS_HOST=server-redis
REDIS_PORT=6379
REDIS_PASSWORD=""
# Service-specific variables
MY_SERVICE_TRAEFIK_HOST=my-service.ourbazinga.space
MY_SERVICE_EXTERNAL_PORT=30100
MY_SERVICE_INTERNAL_PORT=3000
MY_SERVICE_ADMIN_USER=admin
MY_SERVICE_ADMIN_PASSWORD=generate_secure_password_here
MY_SERVICE_API_KEY=generate_secure_api_key_here
Key Rules:
- Never commit plaintext passwords to git (
.gitignorealready excludes.envfiles) - For sensitive values, add to
.env.secrets.masterand source it:source /home/docker/stacks/.env.secrets.master - Use consistent naming:
{SERVICE_NAME}_TRAEFIK_HOST,{SERVICE_NAME}_EXTERNAL_PORT, etc.
Step 2: Create docker-compose.yml
Template for a typical Bazinga service:
# ==============================================================================
# SERVICE: my-service
# Description: [Service description]
# External Port: 30100 (exposed to Traefik and host)
# Internal Port: 3000 (app listens on this inside container)
# External URL : my-service.ourbazinga.space (HTTPS via Traefik on Billie)
# Variables come from .env
# ==============================================================================
services:
my-service:
image: organization/my-service:latest
container_name: my-service
hostname: my-service
restart: ${RESTART}
# ==========================================================================
# Traefik Labels
# For local stacks: Traefik labels auto-route via docker provider
# For remote stacks: Use file-provider config instead (see Step 4)
# ==========================================================================
labels:
- "traefik.enable=${NETWORKS_EXTERNAL}"
- "traefik.docker.network=${NETWORK}"
- "traefik.http.routers.my-service.rule=Host(\`${MY_SERVICE_TRAEFIK_HOST}\`)"
- "traefik.http.routers.my-service.entrypoints=websecure"
- "traefik.http.routers.my-service.tls.certresolver=letsencrypt"
- "traefik.http.services.my-service.loadbalancer.server.port=${MY_SERVICE_INTERNAL_PORT}"
# ==========================================================================
# Resource Limits
# ==========================================================================
deploy:
resources:
limits:
cpus: ${CPUS}
memory: ${MEMORY}
# ==========================================================================
# Environment Variables
# ==========================================================================
env_file:
- .env
environment:
- TZ=${TZ}
- PUID=${PUID}
- PGID=${PGID}
- DATABASE_HOST=${POSTGRES_HOST}
- DATABASE_PORT=${POSTGRES_PORT}
- DATABASE_USER=${POSTGRES_USER}
- DATABASE_PASSWORD=${POSTGRES_PASSWORD}
- DATABASE_NAME=my_service_db
- REDIS_URL=redis://${REDIS_HOST}:${REDIS_PORT}
- APP_PORT=${MY_SERVICE_INTERNAL_PORT}
- NODE_ENV=production
# ==========================================================================
# Volumes (persistent data + config)
# ==========================================================================
volumes:
- ./config:/app/config
- ./data:/app/data
# ==========================================================================
# Networking
# ==========================================================================
ports:
- "${MY_SERVICE_EXTERNAL_PORT}:${MY_SERVICE_INTERNAL_PORT}"
networks:
- ${NETWORK}
# ==============================================================================
# GLOBAL SECTIONS
# ==============================================================================
networks:
bazinga-net:
external: ${NETWORKS_EXTERNAL}
Port Mapping:
- Format:
EXTERNAL_PORT:INTERNAL_PORT - EXTERNAL_PORT (left): exposed on the host, routed through Traefik
- INTERNAL_PORT (right): what the app listens on inside the container
- Example:
30100:3000→ app listens on 3000, accessible on 30100 from outside
Step 3: Register Traefik Hostname
Add your service to /home/docker/stacks/.env.global:
# In "TRAEFIK HOST REGISTRY" section (around line 24-80):
MY_SERVICE_TRAEFIK_HOST=my-service.ourbazinga.space
# In "PORT ALLOCATION REGISTRY" section (around line 84-138):
MY_SERVICE_EXTERNAL_PORT=30100
MY_SERVICE_INTERNAL_PORT=3000
Step 4: Configure Traefik Routing
Option A: Local Stack (Same machine as Traefik)
If your stack runs on Billie (where Traefik runs), the docker labels automatically configure routing. No additional step needed.
Option B: Remote Stack (Different machine)
If your stack runs on Freya, Mabel, Steve, Percy, or Lara, create a Traefik config file on Billie:
File: /home/docker/stacks/server-traefik/config/dynamic/{machine}-{stackname}.yaml
Example: /home/docker/stacks/server-traefik/config/dynamic/freya-my-service.yaml
# my-service on Freya (192.168.50.245), fronted by Billie's Traefik
# Added 2026-09-26 — [service description]
http:
routers:
my-service:
rule: "Host(`my-service.ourbazinga.space`)"
entryPoints:
- websecure
service: my-service
tls:
certResolver: letsencrypt
services:
my-service:
loadBalancer:
servers:
- url: "http://192.168.50.245:30100" # Remote IP + EXTERNAL_PORT
Key points:
- Router name must be globally unique (use lowercase service name)
- Service name matches router name
- URL uses remote machine IP and external port
- TLS handled by Traefik (automatic Let's Encrypt certs)
Step 5: Database Setup (if needed)
Using Shared PostgreSQL
For services that need PostgreSQL:
# Create the database
ssh 192.168.50.245 # or relevant machine
docker exec server-postgres psql -U root -c "CREATE DATABASE my_service_db;"
In .env:
POSTGRES_HOST=server-postgres
POSTGRES_PORT=5432
POSTGRES_USER=root
POSTGRES_PASSWORD=root_password
DATABASE_NAME=my_service_db
Using Isolated Database
Add to the same docker-compose.yml:
services:
postgres:
image: postgres:16-alpine
container_name: my-service-postgres
environment:
POSTGRES_DB: my_service_db
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- ./data/postgres:/var/lib/postgresql/data
networks:
- ${NETWORK}
Step 6: Deploy
cd /home/docker/stacks/my-service
docker compose up -d
docker compose ps # Check status
docker compose logs -f # View logs
docker compose down # Stop when done
Step 7: Verify
Test Direct Port Access
curl -s -o /dev/null -w "%{http_code}\n" http://192.168.50.245:30100
# Should return 200 (or service-specific code)
Test HTTPS via Traefik
curl -s -o /dev/null -w "%{http_code}\n" https://my-service.ourbazinga.space
# Should return 200
From Inside Container
docker exec my-service curl -s http://localhost:3000/health
# Should return expected response
Troubleshooting
502 Bad Gateway
Cause: Traefik cannot reach the backend.
Check:
- Service running:
docker ps | grep my-service - Answers on correct port:
docker exec my-service curl http://localhost:3000 - Port mapping correct:
docker compose ps(check PORTS column) - Traefik config correct:
cat /home/docker/stacks/server-traefik/config/dynamic/{machine}-{stackname}.yaml - Direct access works:
curl http://REMOTE_IP:EXTERNAL_PORT
Connection Refused
Cause: Port not listening or service crashed.
Check:
- Container logs:
docker logs --tail 50 my-service - Internal port matches app: verify docker-compose.yml port
- Resource limits sufficient: increase
CPUSorMEMORY - Test image:
docker run --rm image-name
Certificate Error
Cause: Let's Encrypt cert not issued or DNS misconfigured.
Check:
- Traefik logs:
docker logs server-traefik | grep -i cert - DNS resolution:
nslookup my-service.ourbazinga.space - Wait for cert issuance (usually within minutes)
Port Already in Use
Cause: Another process using the port.
Check:
- See what's using it:
lsof -i :30100 - Choose different port from registry
- Kill if necessary:
sudo lsof -ti :30100 | xargs kill -9
Port Allocation Registry
Central ports list in /home/docker/stacks/.env.global (lines 83–138). Choose unused port from range 30000–30799.
Best Practices
- Use env vars — never hardcode values
- Persistent volumes — always mount data directories
- Resource limits — set
CPUSandMEMORY - Health checks — add
healthchecksection (optional but recommended) - Secrets — use
.env.secrets.masterfor sensitive values - Naming — lowercase, hyphens only (
my-service, notMyService) - Documentation — comment docker-compose.yml
- Test first — verify direct access before Traefik
- Backup volumes — configure via
server-duplicati - Monitoring — use
docker logsfor debugging
Common Patterns
Simple Web App (no DB)
services:
my-app:
image: nginx:latest
ports:
- "30100:80"
volumes:
- ./config:/etc/nginx:ro
networks:
- bazinga-net
App + PostgreSQL
services:
app:
image: myapp:latest
ports:
- "30100:3000"
environment:
DATABASE_URL: postgres://user:pass@postgres:5432/mydb
networks:
- bazinga-net
depends_on:
- postgres
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: mydb
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
volumes:
- ./data/postgres:/var/lib/postgresql/data
networks:
- bazinga-net
When to Contact Michelle
- Choosing an external port (check first to avoid conflicts)
- Adding auth middleware (Authelia, SSO, etc.)
- Cross-machine setup issues
- Permission errors on volumes
- Traefik troubleshooting
Related Docs
- Traefik:
/home/docker/stacks/server-traefik/README.md - Global Config:
/home/docker/stacks/.env.global - Machine IPs: See CLAUDE.md
Version: 0.1 | Updated: 2026-09-26 | Examples: mkdocs, vikunja, tududi