Deferred findings from the March 27 security audit, plus a bug fix: MongoDB keyfile (bug fix): - Generate replica.key on first boot via entrypoint script - Fixes crash from --auth + --keyFile without an existing keyfile - Applied to docker-compose.yml, docker-compose.prod.yml, CCP template I7 — Ticket overselling prevention (reservation pattern): - Add reservedCount field to TicketTier schema - Atomically increment reservedCount inside transaction on checkout - Release reservation on checkout.session.completed (webhook) - Release reservation on checkout.session.expired (webhook) - Include reservedCount in availability calculations I17 — Move refresh token to httpOnly cookie: - Server sets httpOnly SameSite=Strict cookie on login/register/refresh - Cookie scoped to /api/auth path, secure in production - Refresh/logout endpoints read from cookie (with body fallback for compat) - Frontend no longer stores refreshToken in localStorage - Auth store simplified: removed refreshToken from state + persistence - API interceptor uses withCredentials:true for automatic cookie sending - Updated media-api, media-public-api, QuickJoinPage, volunteer-invite - Renamed getTokens → getAccessToken across all media components - Install cookie-parser middleware L2 — FeatureGate loading state: - Show Skeleton instead of children while settings are loading - Prevents briefly exposing disabled feature pages Bunker Admin
1086 lines
36 KiB
Handlebars
1086 lines
36 KiB
Handlebars
# Changemaker Lite — Instance: {{name}}
|
|
# Compose project: {{composeProject}}
|
|
# Generated by CCP
|
|
|
|
services:
|
|
# ─── Core Infrastructure ───────────────────────────────────
|
|
|
|
v2-postgres:
|
|
image: {{registryUrl}}/postgres:16-alpine
|
|
container_name: {{containerPrefix}}-postgres
|
|
restart: unless-stopped
|
|
environment:
|
|
POSTGRES_USER: changemaker
|
|
POSTGRES_PASSWORD: "{{secrets.postgresPassword}}"
|
|
POSTGRES_DB: changemaker_v2
|
|
volumes:
|
|
- {{containerPrefix}}-postgres-data:/var/lib/postgresql/data
|
|
- ./api/prisma/init-nocodb-db.sh:/docker-entrypoint-initdb.d/10-init-nocodb.sh:ro
|
|
- ./api/prisma/init-gancio-db.sh:/docker-entrypoint-initdb.d/20-init-gancio.sh:ro
|
|
- ./api/prisma/init-gitea-db.sh:/docker-entrypoint-initdb.d/30-init-gitea.sh:ro
|
|
ports:
|
|
- "127.0.0.1:{{ports.postgres}}:5432"
|
|
networks:
|
|
- {{networkName}}
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U changemaker -d changemaker_v2"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
|
|
redis:
|
|
image: {{registryUrl}}/redis:7-alpine
|
|
container_name: {{containerPrefix}}-redis
|
|
restart: unless-stopped
|
|
command: "redis-server --appendonly yes --maxmemory 512mb --maxmemory-policy noeviction --requirepass {{secrets.redisPassword}}"
|
|
volumes:
|
|
- {{containerPrefix}}-redis-data:/data
|
|
networks:
|
|
- {{networkName}}
|
|
healthcheck:
|
|
test: ["CMD", "redis-cli", "-a", "{{secrets.redisPassword}}", "ping"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
cpus: '1'
|
|
memory: 512M
|
|
reservations:
|
|
cpus: '0.25'
|
|
memory: 256M
|
|
logging:
|
|
driver: "json-file"
|
|
options:
|
|
max-size: "5m"
|
|
max-file: "2"
|
|
|
|
# ─── Application Services ──────────────────────────────────
|
|
|
|
api:
|
|
{{#if useRegistry}}
|
|
image: {{registryUrl}}/changemaker-api:{{imageTag}}
|
|
{{else}}
|
|
build:
|
|
context: ./api
|
|
dockerfile: Dockerfile
|
|
target: development
|
|
{{/if}}
|
|
container_name: {{containerPrefix}}-api
|
|
restart: unless-stopped
|
|
depends_on:
|
|
v2-postgres:
|
|
condition: service_healthy
|
|
redis:
|
|
condition: service_healthy
|
|
env_file: .env
|
|
environment:
|
|
DATABASE_URL: "postgresql://changemaker:{{secrets.postgresPassword}}@{{containerPrefix}}-postgres:5432/changemaker_v2"
|
|
REDIS_URL: "redis://:{{secrets.redisPassword}}@{{containerPrefix}}-redis:6379"
|
|
PORT: "4000"
|
|
NAR_DATA_DIR: /data
|
|
LISTMONK_URL: http://{{containerPrefix}}-listmonk:9000
|
|
ADMIN_URL: https://app.{{domain}}
|
|
API_URL: https://api.{{domain}}
|
|
{{#if enableGancio}}
|
|
GANCIO_URL: http://{{containerPrefix}}-gancio:13120
|
|
{{/if}}
|
|
ENABLE_MEET: "{{#if enableMeet}}true{{else}}false{{/if}}"
|
|
ENABLE_SMS: "{{#if enableSms}}true{{else}}false{{/if}}"
|
|
ENABLE_SOCIAL: "{{#if enableSocial}}true{{else}}false{{/if}}"
|
|
ENABLE_PEOPLE: "{{#if enablePeople}}true{{else}}false{{/if}}"
|
|
{{#if enableMeet}}
|
|
JITSI_APP_ID: changemaker
|
|
JITSI_APP_SECRET: "{{secrets.jitsiAppSecret}}"
|
|
JITSI_URL: http://{{containerPrefix}}-jitsi-web:80
|
|
{{/if}}
|
|
{{#if enableChat}}
|
|
ROCKETCHAT_URL: http://{{containerPrefix}}-rocketchat:3000
|
|
{{/if}}
|
|
ports:
|
|
- "{{ports.api}}:4000"
|
|
{{#if enableListmonk}}
|
|
- "9002:9002"
|
|
{{/if}}
|
|
volumes:
|
|
- ./assets/uploads:/app/uploads
|
|
- ./mkdocs:/mkdocs:rw
|
|
- ./data:/data:ro
|
|
- ./data/upgrade:/app/upgrade:rw
|
|
- ./configs:/app/configs:ro
|
|
networks:
|
|
- {{networkName}}
|
|
healthcheck:
|
|
test: ["CMD", "wget", "-q", "--spider", "http://localhost:4000/api/health"]
|
|
interval: 15s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 30s
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
cpus: '2'
|
|
memory: 1G
|
|
reservations:
|
|
cpus: '0.25'
|
|
memory: 256M
|
|
|
|
admin:
|
|
{{#if useRegistry}}
|
|
image: {{registryUrl}}/changemaker-admin:{{imageTag}}
|
|
{{else}}
|
|
build:
|
|
context: ./admin
|
|
target: development
|
|
{{/if}}
|
|
container_name: {{containerPrefix}}-admin
|
|
restart: unless-stopped
|
|
depends_on:
|
|
- api
|
|
environment:
|
|
DOMAIN: {{domain}}
|
|
NODE_ENV: production
|
|
VITE_API_URL: http://{{containerPrefix}}-api:4000
|
|
VITE_MKDOCS_URL: http://{{containerPrefix}}-mkdocs:8000
|
|
VITE_DOMAIN: {{domain}}
|
|
VITE_MKDOCS_SITE_PORT: "{{math ports.embed "+" 14}}"
|
|
{{#if enableMedia}}
|
|
VITE_MEDIA_API_URL: http://{{containerPrefix}}-media-api:4100
|
|
{{/if}}
|
|
ports:
|
|
- "{{ports.admin}}:3000"
|
|
networks:
|
|
- {{networkName}}
|
|
healthcheck:
|
|
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:3000/"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 20s
|
|
|
|
{{#if enableMedia}}
|
|
media-api:
|
|
{{#if useRegistry}}
|
|
image: {{registryUrl}}/changemaker-media-api:{{imageTag}}
|
|
{{else}}
|
|
build:
|
|
context: ./api
|
|
dockerfile: Dockerfile.media
|
|
target: development
|
|
{{/if}}
|
|
container_name: {{containerPrefix}}-media-api
|
|
restart: unless-stopped
|
|
depends_on:
|
|
v2-postgres:
|
|
condition: service_healthy
|
|
redis:
|
|
condition: service_healthy
|
|
env_file: .env
|
|
environment:
|
|
DATABASE_URL: "postgresql://changemaker:{{secrets.postgresPassword}}@{{containerPrefix}}-postgres:5432/changemaker_v2"
|
|
REDIS_URL: "redis://:{{secrets.redisPassword}}@{{containerPrefix}}-redis:6379"
|
|
MEDIA_API_PORT: "4100"
|
|
CORS_ORIGINS: https://app.{{domain}},http://localhost:{{ports.admin}}
|
|
ENABLE_MEDIA_FEATURES: "true"
|
|
MEDIA_ROOT: /media/local
|
|
MEDIA_UPLOADS: /media/uploads
|
|
volumes:
|
|
- ./media:/media:ro
|
|
- ./media/local/inbox:/media/local/inbox:rw
|
|
- ./media/local/thumbnails:/media/local/thumbnails:rw
|
|
- ./media/local/photos:/media/local/photos:rw
|
|
- ./media/public:/media/public:rw
|
|
networks:
|
|
- {{networkName}}
|
|
healthcheck:
|
|
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:4100/health"]
|
|
interval: 15s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 30s
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
cpus: '2'
|
|
memory: 1G
|
|
reservations:
|
|
cpus: '0.25'
|
|
memory: 256M
|
|
{{/if}}
|
|
|
|
# ─── Reverse Proxy ─────────────────────────────────────────
|
|
|
|
nginx:
|
|
image: {{registryUrl}}/nginx:alpine
|
|
container_name: {{containerPrefix}}-nginx
|
|
restart: unless-stopped
|
|
depends_on:
|
|
- api
|
|
- admin
|
|
volumes:
|
|
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
|
- ./nginx/conf.d:/etc/nginx/conf.d:ro
|
|
ports:
|
|
- "{{ports.nginx}}:80"
|
|
- "{{math ports.embed "+" 0}}:8881" # NocoDB embed proxy
|
|
- "{{math ports.embed "+" 1}}:8882" # n8n embed proxy
|
|
- "{{math ports.embed "+" 2}}:8883" # Gitea embed proxy
|
|
- "{{math ports.embed "+" 3}}:8884" # MailHog embed proxy
|
|
- "{{math ports.embed "+" 4}}:8885" # Mini QR embed proxy
|
|
- "{{math ports.embed "+" 5}}:8886" # Excalidraw embed proxy
|
|
- "{{math ports.embed "+" 6}}:8887" # Homepage embed proxy
|
|
- "{{math ports.embed "+" 7}}:8888" # Code Server embed proxy
|
|
- "{{math ports.embed "+" 8}}:8889" # MkDocs embed proxy
|
|
- "{{math ports.embed "+" 9}}:8890" # Vaultwarden embed proxy
|
|
- "{{math ports.embed "+" 10}}:8891" # Rocket.Chat embed proxy
|
|
- "{{math ports.embed "+" 11}}:8892" # Gancio embed proxy
|
|
- "{{math ports.embed "+" 12}}:8893" # Grafana embed proxy
|
|
- "{{math ports.embed "+" 13}}:8894" # Listmonk embed proxy
|
|
- "{{math ports.embed "+" 14}}:8895" # MkDocs site embed proxy
|
|
- "{{math ports.embed "+" 15}}:8896" # Jitsi Meet embed proxy
|
|
networks:
|
|
- {{networkName}}
|
|
healthcheck:
|
|
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:80/"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 3
|
|
|
|
# ─── Supporting Services ───────────────────────────────────
|
|
|
|
nocodb-v2:
|
|
image: {{registryUrl}}/nocodb:0.301.3
|
|
container_name: {{containerPrefix}}-nocodb
|
|
restart: unless-stopped
|
|
depends_on:
|
|
v2-postgres:
|
|
condition: service_healthy
|
|
environment:
|
|
NC_DB: "pg://{{containerPrefix}}-postgres:5432?u=changemaker&p={{secrets.postgresPassword}}&d=nocodb_meta"
|
|
NC_ADMIN_EMAIL: "{{secrets.adminEmail}}"
|
|
NC_ADMIN_PASSWORD: "{{secrets.nocodbAdminPassword}}"
|
|
volumes:
|
|
- {{containerPrefix}}-nocodb-data:/usr/app/data
|
|
networks:
|
|
- {{networkName}}
|
|
|
|
mailhog:
|
|
image: {{registryUrl}}/mailhog:v1.0.1
|
|
container_name: {{containerPrefix}}-mailhog
|
|
restart: unless-stopped
|
|
networks:
|
|
- {{networkName}}
|
|
logging:
|
|
driver: "json-file"
|
|
options:
|
|
max-size: "5m"
|
|
max-file: "2"
|
|
|
|
mkdocs:
|
|
image: {{registryUrl}}/mkdocs-material:latest
|
|
container_name: {{containerPrefix}}-mkdocs
|
|
restart: unless-stopped
|
|
volumes:
|
|
- ./mkdocs:/docs:rw
|
|
- ./assets/images:/docs/assets/images:rw
|
|
user: "1000:1000"
|
|
environment:
|
|
SITE_URL: https://{{domain}}
|
|
ADMIN_PORT: "{{ports.admin}}"
|
|
ADMIN_URL: https://app.{{domain}}
|
|
BASE_DOMAIN: https://{{domain}}
|
|
API_URL: https://api.{{domain}}
|
|
API_PORT: "{{ports.api}}"
|
|
{{#if enableMedia}}
|
|
MEDIA_API_PUBLIC_URL: https://media.{{domain}}
|
|
MEDIA_API_PORT: "4100"
|
|
{{/if}}
|
|
{{#if enableGancio}}
|
|
GANCIO_URL: http://{{containerPrefix}}-gancio:13120
|
|
GANCIO_PORT: "8092"
|
|
{{/if}}
|
|
command: serve --dev-addr=0.0.0.0:8000 --watch-theme --livereload
|
|
networks:
|
|
- {{networkName}}
|
|
|
|
{{#if enableListmonk}}
|
|
listmonk-db:
|
|
image: {{registryUrl}}/postgres:17-alpine
|
|
container_name: {{containerPrefix}}-listmonk-db
|
|
restart: unless-stopped
|
|
environment:
|
|
POSTGRES_USER: listmonk
|
|
POSTGRES_PASSWORD: "{{secrets.listmonkAdminPassword}}"
|
|
POSTGRES_DB: listmonk
|
|
volumes:
|
|
- {{containerPrefix}}-listmonk-data:/var/lib/postgresql/data
|
|
networks:
|
|
- {{networkName}}
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U listmonk"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 6
|
|
|
|
listmonk-app:
|
|
image: {{registryUrl}}/listmonk:v6.0.0
|
|
container_name: {{containerPrefix}}-listmonk
|
|
restart: unless-stopped
|
|
depends_on:
|
|
listmonk-db:
|
|
condition: service_healthy
|
|
command: [sh, -c, "./listmonk --install --idempotent --yes --config '' && ./listmonk --upgrade --yes --config '' && ./listmonk --config ''"]
|
|
environment:
|
|
LISTMONK_app__address: "0.0.0.0:9000"
|
|
LISTMONK_db__host: {{containerPrefix}}-listmonk-db
|
|
LISTMONK_db__port: "5432"
|
|
LISTMONK_db__user: listmonk
|
|
LISTMONK_db__password: "{{secrets.listmonkAdminPassword}}"
|
|
LISTMONK_db__database: listmonk
|
|
LISTMONK_db__ssl_mode: disable
|
|
TZ: Etc/UTC
|
|
LISTMONK_ADMIN_USER: admin
|
|
LISTMONK_ADMIN_PASSWORD: "{{secrets.listmonkAdminPassword}}"
|
|
volumes:
|
|
- ./assets/uploads:/listmonk/uploads:rw
|
|
networks:
|
|
- {{networkName}}
|
|
healthcheck:
|
|
test: ["CMD", "wget", "-q", "--spider", "http://localhost:9000/"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 30s
|
|
|
|
listmonk-init:
|
|
image: {{registryUrl}}/postgres:17-alpine
|
|
container_name: {{containerPrefix}}-listmonk-init
|
|
depends_on:
|
|
listmonk-app:
|
|
condition: service_started
|
|
restart: "no"
|
|
environment:
|
|
PGPASSWORD: "{{secrets.listmonkAdminPassword}}"
|
|
LISTMONK_API_USER: v2-api
|
|
LISTMONK_API_TOKEN: "{{secrets.listmonkApiToken}}"
|
|
LISTMONK_SMTP_HOST: {{containerPrefix}}-mailhog
|
|
LISTMONK_SMTP_PORT: "1025"
|
|
entrypoint: ["/bin/sh", "-c"]
|
|
command:
|
|
- |
|
|
echo "[listmonk-init] Waiting for Listmonk tables..."
|
|
for i in $$(seq 1 30); do
|
|
if psql -h {{containerPrefix}}-listmonk-db -U listmonk -d listmonk -c "SELECT 1 FROM users LIMIT 1" >/dev/null 2>&1; then
|
|
break
|
|
fi
|
|
sleep 2
|
|
done
|
|
|
|
if [ -n "$$LISTMONK_API_TOKEN" ]; then
|
|
echo "[listmonk-init] Upserting API user '$$LISTMONK_API_USER'..."
|
|
psql -h {{containerPrefix}}-listmonk-db -U listmonk -d listmonk -q <<SQL
|
|
INSERT INTO users (username, password, password_login, email, name, type, user_role_id, status)
|
|
VALUES ('$$LISTMONK_API_USER', '$$LISTMONK_API_TOKEN', true, '$$LISTMONK_API_USER@api.internal', '$$LISTMONK_API_USER', 'api', 1, 'enabled')
|
|
ON CONFLICT (username) DO UPDATE SET password = EXCLUDED.password, status = 'enabled', user_role_id = 1;
|
|
SQL
|
|
echo "[listmonk-init] API user configured"
|
|
else
|
|
echo "[listmonk-init] LISTMONK_API_TOKEN not set, skipping API user"
|
|
fi
|
|
|
|
MAILHOG_ENTRY='{"host":"{{containerPrefix}}-mailhog","port":1025,"username":"","password":"","tls_type":"none","auth_protocol":"none","enabled":true,"max_conns":5,"idle_timeout":"15s","wait_timeout":"5s","max_msg_retries":2,"tls_skip_verify":false,"email_headers":[],"hello_hostname":""}'
|
|
SMTP_VALUE="[$$MAILHOG_ENTRY]"
|
|
psql -h {{containerPrefix}}-listmonk-db -U listmonk -d listmonk -q <<SQL
|
|
UPDATE settings SET value = '$$SMTP_VALUE' WHERE key = 'smtp';
|
|
SQL
|
|
echo "[listmonk-init] SMTP configured"
|
|
echo "[listmonk-init] Done"
|
|
networks:
|
|
- {{networkName}}
|
|
{{/if}}
|
|
|
|
{{#if enableGancio}}
|
|
# Gancio config writer — must complete before Gancio starts.
|
|
# Without config.json, Gancio enters first-time setup mode and crashes
|
|
# with "Non empty db!" if the database was previously initialized.
|
|
gancio-config:
|
|
image: {{registryUrl}}/alpine:3
|
|
container_name: {{containerPrefix}}-gancio-config
|
|
depends_on:
|
|
v2-postgres:
|
|
condition: service_healthy
|
|
volumes:
|
|
- {{containerPrefix}}-gancio-data:/gancio-data
|
|
entrypoint: ["/bin/sh", "-c"]
|
|
command:
|
|
- |
|
|
if [ ! -f /gancio-data/config.json ]; then
|
|
cat > /gancio-data/config.json <<'EOF'
|
|
{
|
|
"baseurl": "https://events.{{domain}}",
|
|
"title": "Events",
|
|
"description": "Community Events",
|
|
"server": { "host": "0.0.0.0", "port": 13120 },
|
|
"db": {
|
|
"dialect": "postgres",
|
|
"host": "{{containerPrefix}}-postgres",
|
|
"port": 5432,
|
|
"database": "gancio",
|
|
"username": "changemaker",
|
|
"password": "{{secrets.postgresPassword}}"
|
|
}
|
|
}
|
|
EOF
|
|
echo "[gancio-config] config.json created"
|
|
else
|
|
echo "[gancio-config] config.json already exists, skipping"
|
|
fi
|
|
restart: "no"
|
|
networks:
|
|
- {{networkName}}
|
|
|
|
gancio:
|
|
image: {{registryUrl}}/gancio:1.28.2
|
|
container_name: {{containerPrefix}}-gancio
|
|
restart: unless-stopped
|
|
depends_on:
|
|
v2-postgres:
|
|
condition: service_healthy
|
|
gancio-config:
|
|
condition: service_completed_successfully
|
|
environment:
|
|
GANCIO_DATA: /home/node/data
|
|
NODE_ENV: production
|
|
GANCIO_DB_DIALECT: postgres
|
|
GANCIO_DB_HOST: {{containerPrefix}}-postgres
|
|
GANCIO_DB_PORT: "5432"
|
|
GANCIO_DB_DATABASE: gancio
|
|
GANCIO_DB_USERNAME: changemaker
|
|
GANCIO_DB_PASSWORD: "{{secrets.postgresPassword}}"
|
|
server__baseurl: https://events.{{domain}}
|
|
volumes:
|
|
- {{containerPrefix}}-gancio-data:/home/node/data
|
|
networks:
|
|
- {{networkName}}
|
|
healthcheck:
|
|
test: ["CMD", "node", "-e", "require('http').get('http://localhost:13120/', r => process.exit(r.statusCode < 400 ? 0 : 1)).on('error', () => process.exit(1))"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 5
|
|
start_period: 60s
|
|
|
|
# Gancio post-start seeder — seeds theme settings after Gancio creates its tables
|
|
gancio-init:
|
|
image: {{registryUrl}}/postgres:16-alpine
|
|
container_name: {{containerPrefix}}-gancio-init
|
|
depends_on:
|
|
gancio:
|
|
condition: service_healthy
|
|
environment:
|
|
PGHOST: {{containerPrefix}}-postgres
|
|
PGUSER: changemaker
|
|
PGPASSWORD: "{{secrets.postgresPassword}}"
|
|
PGDATABASE: gancio
|
|
entrypoint: ["/bin/sh", "-c"]
|
|
command:
|
|
- |
|
|
echo "[gancio-init] Seeding Gancio default theme settings..."
|
|
psql -c "INSERT INTO settings (key, value, is_secret, \"createdAt\", \"updatedAt\") VALUES
|
|
('dark_colors', '{\"primary\": \"#FF6E40\", \"error\": \"#FF5252\", \"info\": \"#2196F3\", \"success\": \"#4CAF50\", \"warning\": \"#FB8C00\"}', false, NOW(), NOW()),
|
|
('light_colors', '{\"primary\": \"#FF4500\", \"error\": \"#FF5252\", \"info\": \"#2196F3\", \"success\": \"#4CAF50\", \"warning\": \"#FB8C00\"}', false, NOW(), NOW())
|
|
ON CONFLICT (key) DO NOTHING;"
|
|
echo "[gancio-init] Theme settings seeded"
|
|
echo "[gancio-init] Done"
|
|
restart: "no"
|
|
networks:
|
|
- {{networkName}}
|
|
{{/if}}
|
|
|
|
{{#if enableChat}}
|
|
nats-rocketchat:
|
|
image: {{registryUrl}}/nats:2.11-alpine
|
|
container_name: {{containerPrefix}}-nats
|
|
restart: unless-stopped
|
|
command: --http_port 8222
|
|
networks:
|
|
- {{networkName}}
|
|
|
|
mongodb-rocketchat:
|
|
image: {{registryUrl}}/mongo:6.0
|
|
container_name: {{containerPrefix}}-mongodb
|
|
restart: unless-stopped
|
|
entrypoint: ["/bin/bash", "-c", "if [ ! -f /data/replica.key ]; then openssl rand -base64 756 > /data/replica.key; fi && chmod 400 /data/replica.key && chown 999:999 /data/replica.key && exec mongod --replSet rs0 --bind_ip_all --auth --keyFile /data/replica.key"]
|
|
environment:
|
|
MONGO_INITDB_ROOT_USERNAME: "${MONGO_ROOT_USER:-rocketchat}"
|
|
MONGO_INITDB_ROOT_PASSWORD: "${MONGO_ROOT_PASSWORD}"
|
|
volumes:
|
|
- {{containerPrefix}}-mongodb-data:/data/db
|
|
networks:
|
|
- {{networkName}}
|
|
healthcheck:
|
|
test: ["CMD", "mongosh", "-u", "${MONGO_ROOT_USER:-rocketchat}", "-p", "${MONGO_ROOT_PASSWORD}", "--authenticationDatabase", "admin", "--quiet", "--eval", "try { rs.status().ok } catch(e) { rs.initiate({_id:'rs0',members:[{_id:0,host:'{{containerPrefix}}-mongodb:27017'}]}).ok }"]
|
|
interval: 10s
|
|
timeout: 10s
|
|
retries: 10
|
|
start_period: 30s
|
|
|
|
rocketchat:
|
|
image: {{registryUrl}}/rocketchat:7.9.7
|
|
container_name: {{containerPrefix}}-rocketchat
|
|
restart: unless-stopped
|
|
depends_on:
|
|
mongodb-rocketchat:
|
|
condition: service_healthy
|
|
nats-rocketchat:
|
|
condition: service_started
|
|
environment:
|
|
ROOT_URL: http://chat.{{domain}}
|
|
MONGO_URL: mongodb://${MONGO_ROOT_USER:-rocketchat}:${MONGO_ROOT_PASSWORD}@{{containerPrefix}}-mongodb:27017/rocketchat?replicaSet=rs0&authSource=admin
|
|
MONGO_OPLOG_URL: mongodb://${MONGO_ROOT_USER:-rocketchat}:${MONGO_ROOT_PASSWORD}@{{containerPrefix}}-mongodb:27017/local?replicaSet=rs0&authSource=admin
|
|
TRANSPORTER: monolith+nats://{{containerPrefix}}-nats:4222
|
|
PORT: "3000"
|
|
ADMIN_USERNAME: rcadmin
|
|
ADMIN_NAME: Admin
|
|
ADMIN_EMAIL: "{{secrets.adminEmail}}"
|
|
ADMIN_PASS: "{{secrets.rocketchatAdminPassword}}"
|
|
CREATE_TOKENS_FOR_USERS: "true"
|
|
OVERWRITE_SETTING_Iframe_Integration_send_enable: "true"
|
|
OVERWRITE_SETTING_Iframe_Integration_receive_enable: "true"
|
|
OVERWRITE_SETTING_Iframe_Integration_receive_origin: http://app.{{domain}},https://app.{{domain}}
|
|
{{#if enableMeet}}
|
|
OVERWRITE_SETTING_Jitsi_Enabled: "true"
|
|
OVERWRITE_SETTING_Jitsi_Domain: meet.{{domain}}
|
|
OVERWRITE_SETTING_Jitsi_URL_Room_Prefix: RocketChat
|
|
OVERWRITE_SETTING_Jitsi_Enable_Channels: "true"
|
|
OVERWRITE_SETTING_Jitsi_Open_New_Window: "false"
|
|
OVERWRITE_SETTING_Jitsi_Enabled_TokenAuth: "true"
|
|
OVERWRITE_SETTING_Jitsi_Application_ID: changemaker
|
|
OVERWRITE_SETTING_Jitsi_Application_Secret: "{{secrets.jitsiAppSecret}}"
|
|
OVERWRITE_SETTING_VideoConf_Default_Provider: jitsi
|
|
{{/if}}
|
|
volumes:
|
|
- {{containerPrefix}}-rocketchat-uploads:/app/uploads
|
|
networks:
|
|
- {{networkName}}
|
|
healthcheck:
|
|
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:3000/api/info"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 10
|
|
start_period: 90s
|
|
{{/if}}
|
|
|
|
{{#if enablePangolin}}
|
|
# ─── Pangolin Tunnel ───────────────────────────────────────
|
|
|
|
newt:
|
|
image: {{registryUrl}}/newt:latest
|
|
container_name: {{containerPrefix}}-newt
|
|
restart: unless-stopped
|
|
depends_on:
|
|
- nginx
|
|
environment:
|
|
PANGOLIN_ENDPOINT: "{{pangolin.endpoint}}"
|
|
NEWT_ID: "{{pangolin.newtId}}"
|
|
NEWT_SECRET: "{{pangolin.newtSecret}}"
|
|
networks:
|
|
- {{networkName}}
|
|
{{/if}}
|
|
|
|
{{#if enableMeet}}
|
|
# ─── Jitsi Meet (Video Conferencing) ────────────────────
|
|
|
|
jitsi-web:
|
|
image: {{registryUrl}}/jitsi-web:stable-9823
|
|
container_name: {{containerPrefix}}-jitsi-web
|
|
restart: unless-stopped
|
|
depends_on:
|
|
- jitsi-prosody
|
|
environment:
|
|
XMPP_SERVER: {{containerPrefix}}-jitsi-prosody
|
|
XMPP_DOMAIN: meet.jitsi
|
|
XMPP_AUTH_DOMAIN: auth.meet.jitsi
|
|
XMPP_BOSH_URL_BASE: http://{{containerPrefix}}-jitsi-prosody:5280
|
|
XMPP_MUC_DOMAIN: muc.meet.jitsi
|
|
PUBLIC_URL: https://meet.{{domain}}
|
|
TZ: America/Edmonton
|
|
ENABLE_AUTH: "1"
|
|
AUTH_TYPE: jwt
|
|
JWT_APP_ID: changemaker
|
|
JWT_APP_SECRET: "{{secrets.jitsiAppSecret}}"
|
|
JWT_ACCEPTED_ISSUERS: changemaker
|
|
JWT_ACCEPTED_AUDIENCES: changemaker
|
|
volumes:
|
|
- {{containerPrefix}}-jitsi-web-config:/config
|
|
networks:
|
|
- {{networkName}}
|
|
|
|
jitsi-prosody:
|
|
image: {{registryUrl}}/jitsi-prosody:stable-9823
|
|
container_name: {{containerPrefix}}-jitsi-prosody
|
|
restart: unless-stopped
|
|
environment:
|
|
XMPP_DOMAIN: meet.jitsi
|
|
XMPP_AUTH_DOMAIN: auth.meet.jitsi
|
|
XMPP_MUC_DOMAIN: muc.meet.jitsi
|
|
XMPP_INTERNAL_MUC_DOMAIN: internal-muc.meet.jitsi
|
|
XMPP_RECORDER_DOMAIN: recorder.meet.jitsi
|
|
XMPP_CROSS_DOMAIN: "true"
|
|
JICOFO_AUTH_USER: focus
|
|
JICOFO_AUTH_PASSWORD: "{{secrets.jitsiJicofoAuthPassword}}"
|
|
JVB_AUTH_USER: jvb
|
|
JVB_AUTH_PASSWORD: "{{secrets.jitsiJvbAuthPassword}}"
|
|
TZ: America/Edmonton
|
|
ENABLE_AUTH: "1"
|
|
AUTH_TYPE: jwt
|
|
JWT_APP_ID: changemaker
|
|
JWT_APP_SECRET: "{{secrets.jitsiAppSecret}}"
|
|
JWT_ACCEPTED_ISSUERS: changemaker
|
|
JWT_ACCEPTED_AUDIENCES: changemaker
|
|
JWT_ALLOW_EMPTY: "0"
|
|
volumes:
|
|
- {{containerPrefix}}-jitsi-prosody-config:/config
|
|
- {{containerPrefix}}-jitsi-prosody-plugins:/prosody-plugins-custom
|
|
networks:
|
|
- {{networkName}}
|
|
|
|
jitsi-jicofo:
|
|
image: {{registryUrl}}/jitsi-jicofo:stable-9823
|
|
container_name: {{containerPrefix}}-jitsi-jicofo
|
|
restart: unless-stopped
|
|
depends_on:
|
|
- jitsi-prosody
|
|
environment:
|
|
XMPP_SERVER: {{containerPrefix}}-jitsi-prosody
|
|
XMPP_DOMAIN: meet.jitsi
|
|
XMPP_AUTH_DOMAIN: auth.meet.jitsi
|
|
XMPP_INTERNAL_MUC_DOMAIN: internal-muc.meet.jitsi
|
|
XMPP_MUC_DOMAIN: muc.meet.jitsi
|
|
JICOFO_AUTH_USER: focus
|
|
JICOFO_AUTH_PASSWORD: "{{secrets.jitsiJicofoAuthPassword}}"
|
|
TZ: America/Edmonton
|
|
volumes:
|
|
- {{containerPrefix}}-jitsi-jicofo-config:/config
|
|
networks:
|
|
- {{networkName}}
|
|
|
|
jitsi-jvb:
|
|
image: {{registryUrl}}/jitsi-jvb:stable-9823
|
|
container_name: {{containerPrefix}}-jitsi-jvb
|
|
restart: unless-stopped
|
|
depends_on:
|
|
- jitsi-prosody
|
|
environment:
|
|
XMPP_SERVER: {{containerPrefix}}-jitsi-prosody
|
|
XMPP_DOMAIN: meet.jitsi
|
|
XMPP_AUTH_DOMAIN: auth.meet.jitsi
|
|
XMPP_INTERNAL_MUC_DOMAIN: internal-muc.meet.jitsi
|
|
JVB_AUTH_USER: jvb
|
|
JVB_AUTH_PASSWORD: "{{secrets.jitsiJvbAuthPassword}}"
|
|
JVB_STUN_SERVERS: meet-jit-si-turnrelay.jitsi.net:443
|
|
JVB_PORT: "10000"
|
|
JVB_ADVERTISE_IPS: "{{jvbAdvertiseIp}}"
|
|
TZ: America/Edmonton
|
|
ports:
|
|
- "10000:10000/udp"
|
|
volumes:
|
|
- {{containerPrefix}}-jitsi-jvb-config:/config
|
|
networks:
|
|
- {{networkName}}
|
|
{{/if}}
|
|
|
|
# ─── Always-On Utilities ──────────────────────────────────
|
|
|
|
mini-qr:
|
|
image: {{registryUrl}}/mini-qr:v0.26.0
|
|
container_name: {{containerPrefix}}-mini-qr
|
|
restart: unless-stopped
|
|
networks:
|
|
- {{networkName}}
|
|
|
|
mkdocs-site-server:
|
|
image: {{registryUrl}}/nginx:alpine
|
|
container_name: {{containerPrefix}}-mkdocs-site
|
|
restart: unless-stopped
|
|
volumes:
|
|
- ./mkdocs/site:/usr/share/nginx/html:ro
|
|
networks:
|
|
- {{networkName}}
|
|
|
|
{{#if enableDevTools}}
|
|
# ─── Dev Tools ────────────────────────────────────────────
|
|
|
|
code-server:
|
|
image: {{registryUrl}}/code-server:latest
|
|
container_name: {{containerPrefix}}-code-server
|
|
restart: unless-stopped
|
|
environment:
|
|
PASSWORD: "{{secrets.nocodbAdminPassword}}"
|
|
SUDO_PASSWORD: "{{secrets.nocodbAdminPassword}}"
|
|
volumes:
|
|
- .:/config/workspace:rw
|
|
networks:
|
|
- {{networkName}}
|
|
|
|
gitea:
|
|
image: {{registryUrl}}/gitea:1.23.7
|
|
container_name: {{containerPrefix}}-gitea
|
|
restart: unless-stopped
|
|
depends_on:
|
|
v2-postgres:
|
|
condition: service_healthy
|
|
environment:
|
|
GITEA__database__DB_TYPE: postgres
|
|
GITEA__database__HOST: {{containerPrefix}}-postgres:5432
|
|
GITEA__database__NAME: gitea
|
|
GITEA__database__USER: changemaker
|
|
GITEA__database__PASSWD: "{{secrets.postgresPassword}}"
|
|
GITEA__server__ROOT_URL: https://git.{{domain}}
|
|
GITEA__server__DOMAIN: git.{{domain}}
|
|
GITEA__server__HTTP_PORT: "3000"
|
|
GITEA__server__PROTOCOL: http
|
|
GITEA__server__ENABLE_GZIP: "true"
|
|
GITEA__server__X_FRAME_OPTIONS: ""
|
|
GITEA__security__INSTALL_LOCK: "true"
|
|
GITEA__attachment__MAX_SIZE: "1024"
|
|
GITEA__repository__MAX_CREATION_LIMIT: "-1"
|
|
GITEA__server__LFS_START_SERVER: "true"
|
|
volumes:
|
|
- {{containerPrefix}}-gitea-data:/data
|
|
networks:
|
|
- {{networkName}}
|
|
healthcheck:
|
|
test: ["CMD", "curl", "-fsSL", "http://localhost:3000/api/healthz"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 5
|
|
start_period: 30s
|
|
|
|
# Gitea init — creates admin user after Gitea is healthy (idempotent)
|
|
# Must run as git user (UID 1000) — Gitea refuses to run as root
|
|
gitea-init:
|
|
image: {{registryUrl}}/gitea:1.23.7
|
|
container_name: {{containerPrefix}}-gitea-init
|
|
user: "1000:1000"
|
|
depends_on:
|
|
gitea:
|
|
condition: service_healthy
|
|
environment:
|
|
GITEA__database__DB_TYPE: postgres
|
|
GITEA__database__HOST: {{containerPrefix}}-postgres:5432
|
|
GITEA__database__NAME: gitea
|
|
GITEA__database__USER: changemaker
|
|
GITEA__database__PASSWD: "{{secrets.postgresPassword}}"
|
|
GITEA__security__INSTALL_LOCK: "true"
|
|
volumes:
|
|
- {{containerPrefix}}-gitea-data:/data
|
|
entrypoint: ["/bin/sh", "-c"]
|
|
command:
|
|
- |
|
|
echo "[gitea-init] Running migrations (idempotent)..."
|
|
gitea migrate 2>/dev/null || true
|
|
|
|
if gitea admin user list --admin 2>/dev/null | grep -q "admin"; then
|
|
echo "[gitea-init] Admin user already exists, skipping"
|
|
else
|
|
echo "[gitea-init] Creating admin user..."
|
|
gitea admin user create \
|
|
--admin \
|
|
--username admin \
|
|
--password "{{secrets.giteaAdminPassword}}" \
|
|
--email "{{secrets.adminEmail}}" \
|
|
--must-change-password=false
|
|
echo "[gitea-init] Admin user created"
|
|
fi
|
|
|
|
echo "[gitea-init] Done"
|
|
restart: "no"
|
|
networks:
|
|
- {{networkName}}
|
|
|
|
n8n:
|
|
image: {{registryUrl}}/n8n:latest
|
|
container_name: {{containerPrefix}}-n8n
|
|
restart: unless-stopped
|
|
environment:
|
|
N8N_ENCRYPTION_KEY: "{{secrets.n8nEncryptionKey}}"
|
|
WEBHOOK_URL: https://n8n.{{domain}}
|
|
N8N_HOST: n8n.{{domain}}
|
|
N8N_PROTOCOL: https
|
|
N8N_SECURE_COOKIE: "false"
|
|
volumes:
|
|
- {{containerPrefix}}-n8n-data:/home/node/.n8n
|
|
networks:
|
|
- {{networkName}}
|
|
healthcheck:
|
|
test: ["CMD", "wget", "-q", "--spider", "http://localhost:5678/healthz"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 3
|
|
|
|
docker-socket-proxy:
|
|
image: {{registryUrl}}/docker-socket-proxy:v0.4.2
|
|
container_name: {{containerPrefix}}-docker-socket-proxy
|
|
restart: unless-stopped
|
|
environment:
|
|
CONTAINERS: 1
|
|
IMAGES: 1
|
|
INFO: 1
|
|
NETWORKS: 0
|
|
VOLUMES: 0
|
|
POST: 0
|
|
volumes:
|
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
networks:
|
|
- {{networkName}}
|
|
|
|
homepage:
|
|
image: {{registryUrl}}/homepage:v0.7.2
|
|
container_name: {{containerPrefix}}-homepage
|
|
restart: unless-stopped
|
|
environment:
|
|
DOCKER_HOST: tcp://{{containerPrefix}}-docker-socket-proxy:2375
|
|
volumes:
|
|
- {{containerPrefix}}-homepage-data:/app/config
|
|
depends_on:
|
|
- docker-socket-proxy
|
|
networks:
|
|
- {{networkName}}
|
|
|
|
excalidraw:
|
|
image: {{registryUrl}}/excalidraw:latest
|
|
container_name: {{containerPrefix}}-excalidraw
|
|
restart: unless-stopped
|
|
networks:
|
|
- {{networkName}}
|
|
|
|
# Vaultwarden — Password manager (Bitwarden-compatible)
|
|
vaultwarden:
|
|
image: {{registryUrl}}/vaultwarden:1.35.4
|
|
container_name: {{containerPrefix}}-vaultwarden
|
|
restart: unless-stopped
|
|
environment:
|
|
ADMIN_TOKEN: "{{secrets.vaultwardenAdminToken}}"
|
|
DOMAIN: https://vault.{{domain}}
|
|
SIGNUPS_ALLOWED: "false"
|
|
WEBSOCKET_ENABLED: "true"
|
|
ROCKET_PORT: "80"
|
|
LOG_LEVEL: info
|
|
SMTP_HOST: {{containerPrefix}}-mailhog
|
|
SMTP_PORT: "1025"
|
|
SMTP_FROM: "noreply@{{domain}}"
|
|
SMTP_FROM_NAME: Vaultwarden
|
|
SMTP_SECURITY: "off"
|
|
SMTP_USERNAME: ""
|
|
SMTP_PASSWORD: ""
|
|
volumes:
|
|
- {{containerPrefix}}-vaultwarden-data:/data
|
|
networks:
|
|
- {{networkName}}
|
|
healthcheck:
|
|
test: ["CMD", "curl", "-sf", "http://localhost:80/alive"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 15s
|
|
|
|
# Vaultwarden init — invites the initial admin user (safe to re-run)
|
|
vaultwarden-init:
|
|
image: {{registryUrl}}/alpine-curl:8.11.1
|
|
container_name: {{containerPrefix}}-vaultwarden-init
|
|
depends_on:
|
|
vaultwarden:
|
|
condition: service_healthy
|
|
restart: "no"
|
|
entrypoint: ["/bin/sh", "-c"]
|
|
command:
|
|
- |
|
|
echo "[vaultwarden-init] Waiting for Vaultwarden..."
|
|
for i in $$(seq 1 20); do
|
|
if curl -sf http://{{containerPrefix}}-vaultwarden:80/alive >/dev/null 2>&1; then
|
|
break
|
|
fi
|
|
sleep 2
|
|
done
|
|
|
|
VAULTWARDEN_ADMIN_TOKEN="{{secrets.vaultwardenAdminToken}}"
|
|
VAULTWARDEN_URL="http://{{containerPrefix}}-vaultwarden:80"
|
|
INVITE_EMAIL="{{secrets.adminEmail}}"
|
|
|
|
if [ -z "$$VAULTWARDEN_ADMIN_TOKEN" ]; then
|
|
echo "[vaultwarden-init] VAULTWARDEN_ADMIN_TOKEN not set, skipping invite"
|
|
exit 0
|
|
fi
|
|
|
|
echo "[vaultwarden-init] Authenticating with admin panel..."
|
|
SESSION_COOKIE=$$(mktemp)
|
|
HTTP_CODE=$$(curl -s -o /dev/null -w "%{http_code}" \
|
|
-c "$$SESSION_COOKIE" \
|
|
-X POST "$$VAULTWARDEN_URL/admin" \
|
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
|
-d "token=$$VAULTWARDEN_ADMIN_TOKEN")
|
|
|
|
if [ "$$HTTP_CODE" != "200" ] && [ "$$HTTP_CODE" != "302" ]; then
|
|
echo "[vaultwarden-init] Admin auth failed (HTTP $$HTTP_CODE)"
|
|
rm -f "$$SESSION_COOKIE"
|
|
exit 1
|
|
fi
|
|
echo "[vaultwarden-init] Authenticated"
|
|
|
|
echo "[vaultwarden-init] Inviting $$INVITE_EMAIL..."
|
|
INVITE_CODE=$$(curl -s -w "\n%{http_code}" \
|
|
-b "$$SESSION_COOKIE" \
|
|
-X POST "$$VAULTWARDEN_URL/admin/invite" \
|
|
-H "Content-Type: application/json" \
|
|
-d "{\"email\":\"$$INVITE_EMAIL\"}")
|
|
|
|
INVITE_HTTP=$$(echo "$$INVITE_CODE" | tail -1)
|
|
INVITE_BODY=$$(echo "$$INVITE_CODE" | head -n -1)
|
|
|
|
if [ "$$INVITE_HTTP" = "200" ] || [ "$$INVITE_HTTP" = "422" ]; then
|
|
echo "[vaultwarden-init] Invite sent (or user already exists)"
|
|
else
|
|
echo "[vaultwarden-init] Invite failed (HTTP $$INVITE_HTTP): $$INVITE_BODY"
|
|
fi
|
|
|
|
rm -f "$$SESSION_COOKIE"
|
|
echo "[vaultwarden-init] Done"
|
|
networks:
|
|
- {{networkName}}
|
|
{{/if}}
|
|
|
|
{{#if enableMonitoring}}
|
|
# ─── Monitoring Stack ──────────────────────────────────────
|
|
|
|
prometheus:
|
|
image: {{registryUrl}}/prometheus:v3.10.0
|
|
container_name: {{containerPrefix}}-prometheus
|
|
restart: unless-stopped
|
|
command:
|
|
- '--config.file=/etc/prometheus/prometheus.yml'
|
|
- '--storage.tsdb.path=/prometheus'
|
|
- '--storage.tsdb.retention.time=30d'
|
|
volumes:
|
|
- ./configs/prometheus:/etc/prometheus:ro
|
|
- {{containerPrefix}}-prometheus-data:/prometheus
|
|
networks:
|
|
- {{networkName}}
|
|
|
|
grafana:
|
|
image: {{registryUrl}}/grafana:12.3.0
|
|
container_name: {{containerPrefix}}-grafana
|
|
restart: unless-stopped
|
|
environment:
|
|
GF_SECURITY_ADMIN_PASSWORD: "{{secrets.grafanaAdminPassword}}"
|
|
GF_USERS_ALLOW_SIGN_UP: "false"
|
|
GF_SERVER_ROOT_URL: https://grafana.{{domain}}
|
|
GF_SECURITY_ALLOW_EMBEDDING: "true"
|
|
volumes:
|
|
- {{containerPrefix}}-grafana-data:/var/lib/grafana
|
|
- ./configs/grafana:/etc/grafana/provisioning
|
|
depends_on:
|
|
- prometheus
|
|
networks:
|
|
- {{networkName}}
|
|
|
|
alertmanager:
|
|
image: {{registryUrl}}/alertmanager:v0.31.1
|
|
container_name: {{containerPrefix}}-alertmanager
|
|
restart: unless-stopped
|
|
command:
|
|
- '--config.file=/etc/alertmanager/alertmanager.yml'
|
|
- '--storage.path=/alertmanager'
|
|
volumes:
|
|
- ./configs/alertmanager:/etc/alertmanager:ro
|
|
- {{containerPrefix}}-alertmanager-data:/alertmanager
|
|
networks:
|
|
- {{networkName}}
|
|
|
|
cadvisor:
|
|
image: {{registryUrl}}/cadvisor:v0.55.1
|
|
container_name: {{containerPrefix}}-cadvisor
|
|
restart: unless-stopped
|
|
volumes:
|
|
- /:/rootfs:ro
|
|
- /var/run:/var/run:ro
|
|
- /sys:/sys:ro
|
|
- /var/lib/docker/:/var/lib/docker:ro
|
|
networks:
|
|
- {{networkName}}
|
|
|
|
node-exporter:
|
|
image: {{registryUrl}}/node-exporter:v1.10.2
|
|
container_name: {{containerPrefix}}-node-exporter
|
|
restart: unless-stopped
|
|
command:
|
|
- '--path.rootfs=/host'
|
|
volumes:
|
|
- /:/host:ro,rslave
|
|
networks:
|
|
- {{networkName}}
|
|
|
|
redis-exporter:
|
|
image: {{registryUrl}}/redis_exporter:v1.81.0
|
|
container_name: {{containerPrefix}}-redis-exporter
|
|
restart: unless-stopped
|
|
environment:
|
|
REDIS_ADDR: redis://{{containerPrefix}}-redis:6379
|
|
REDIS_PASSWORD: "{{secrets.redisPassword}}"
|
|
depends_on:
|
|
- redis
|
|
networks:
|
|
- {{networkName}}
|
|
|
|
gotify:
|
|
image: {{registryUrl}}/gotify:v2.9.0
|
|
container_name: {{containerPrefix}}-gotify
|
|
restart: unless-stopped
|
|
volumes:
|
|
- {{containerPrefix}}-gotify-data:/app/data
|
|
networks:
|
|
- {{networkName}}
|
|
{{/if}}
|
|
|
|
# ─── Volumes ──────────────────────────────────────────────
|
|
|
|
volumes:
|
|
{{containerPrefix}}-postgres-data:
|
|
{{containerPrefix}}-redis-data:
|
|
{{containerPrefix}}-nocodb-data:
|
|
{{#if enableListmonk}}
|
|
{{containerPrefix}}-listmonk-data:
|
|
{{/if}}
|
|
{{#if enableGancio}}
|
|
{{containerPrefix}}-gancio-data:
|
|
{{/if}}
|
|
{{#if enableChat}}
|
|
{{containerPrefix}}-mongodb-data:
|
|
{{containerPrefix}}-rocketchat-uploads:
|
|
{{/if}}
|
|
{{#if enableDevTools}}
|
|
{{containerPrefix}}-gitea-data:
|
|
{{containerPrefix}}-n8n-data:
|
|
{{containerPrefix}}-homepage-data:
|
|
{{containerPrefix}}-vaultwarden-data:
|
|
{{/if}}
|
|
{{#if enableMonitoring}}
|
|
{{containerPrefix}}-prometheus-data:
|
|
{{containerPrefix}}-grafana-data:
|
|
{{containerPrefix}}-alertmanager-data:
|
|
{{containerPrefix}}-gotify-data:
|
|
{{/if}}
|
|
{{#if enableMeet}}
|
|
{{containerPrefix}}-jitsi-web-config:
|
|
{{containerPrefix}}-jitsi-prosody-config:
|
|
{{containerPrefix}}-jitsi-prosody-plugins:
|
|
{{containerPrefix}}-jitsi-jicofo-config:
|
|
{{containerPrefix}}-jitsi-jvb-config:
|
|
{{/if}}
|
|
|
|
# ─── Networks ─────────────────────────────────────────────
|
|
|
|
networks:
|
|
{{networkName}}:
|
|
driver: bridge
|