# Changemaker Lite — Instance: {{name}} # Compose project: {{composeProject}} # Generated by CCP services: # ─── Core Infrastructure ─────────────────────────────────── v2-postgres: image: 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 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: 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: build: context: ./api dockerfile: Dockerfile target: development 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}} ports: - "{{ports.api}}:4000" volumes: - ./api:/app - /app/node_modules - ./assets/uploads:/app/uploads - ./mkdocs:/mkdocs:rw - ./data:/data:ro - ./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: build: context: ./admin target: development 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 {{#if enableMedia}} VITE_MEDIA_API_URL: http://{{containerPrefix}}-media-api:4100 {{/if}} volumes: - ./admin:/app - /app/node_modules 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: build: context: ./api dockerfile: Dockerfile.media target: development 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: - ./api:/app - /app/node_modules - ./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: 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 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: nocodb/nocodb:latest 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: mailhog/mailhog:latest container_name: {{containerPrefix}}-mailhog restart: unless-stopped networks: - {{networkName}} logging: driver: "json-file" options: max-size: "5m" max-file: "2" mkdocs: image: squidfunk/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: 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: listmonk/listmonk:latest 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: 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 < process.exit(r.statusCode < 400 ? 0 : 1)).on('error', () => process.exit(1))"] interval: 30s timeout: 10s retries: 5 start_period: 60s {{/if}} {{#if enableChat}} nats-rocketchat: image: nats:2.11-alpine container_name: {{containerPrefix}}-nats restart: unless-stopped command: --http_port 8222 networks: - {{networkName}} mongodb-rocketchat: image: mongo:6.0 container_name: {{containerPrefix}}-mongodb restart: unless-stopped command: ["mongod", "--replSet", "rs0", "--bind_ip_all"] volumes: - {{containerPrefix}}-mongodb-data:/data/db networks: - {{networkName}} healthcheck: test: ["CMD", "mongosh", "--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: rocketchat/rocket.chat: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://{{containerPrefix}}-mongodb:27017/rocketchat?replicaSet=rs0 MONGO_OPLOG_URL: mongodb://{{containerPrefix}}-mongodb:27017/local?replicaSet=rs0 TRANSPORTER: monolith+nats://{{containerPrefix}}-nats:4222 PORT: "3000" ADMIN_USERNAME: rcadmin ADMIN_NAME: Admin ADMIN_EMAIL: {{secrets.adminEmail}} ADMIN_PASS: {{secrets.nocodbAdminPassword}} 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}} 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}} # ─── Pangolin Tunnel ─────────────────────────────────────── newt: image: fosrl/newt:latest container_name: {{containerPrefix}}-newt restart: unless-stopped depends_on: - nginx environment: PANGOLIN_ENDPOINT: ${PANGOLIN_ENDPOINT} NEWT_ID: ${PANGOLIN_NEWT_ID} NEWT_SECRET: ${PANGOLIN_NEWT_SECRET} networks: - {{networkName}} # ─── Always-On Utilities ────────────────────────────────── mini-qr: image: ghcr.io/lyqht/mini-qr:latest container_name: {{containerPrefix}}-mini-qr restart: unless-stopped networks: - {{networkName}} mkdocs-site-server: image: 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: lscr.io/linuxserver/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: gitea/gitea:latest 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__security__INSTALL_LOCK: "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 n8n: image: n8nio/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 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 homepage: image: ghcr.io/gethomepage/homepage:latest container_name: {{containerPrefix}}-homepage restart: unless-stopped volumes: - {{containerPrefix}}-homepage-data:/app/config - /var/run/docker.sock:/var/run/docker.sock:ro networks: - {{networkName}} excalidraw: image: excalidraw/excalidraw:latest container_name: {{containerPrefix}}-excalidraw restart: unless-stopped networks: - {{networkName}} {{/if}} {{#if enableMonitoring}} # ─── Monitoring Stack ────────────────────────────────────── prometheus: image: prom/prometheus:latest 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: grafana/grafana:latest 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: prom/alertmanager:latest 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}} {{/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: {{/if}} {{#if enableMonitoring}} {{containerPrefix}}-prometheus-data: {{containerPrefix}}-grafana-data: {{containerPrefix}}-alertmanager-data: {{/if}} # ─── Networks ───────────────────────────────────────────── networks: {{networkName}}: driver: bridge