# dev-compose.yml # Full developer tooling stack: Git hosting, browser IDE, container registry, # database UI, secret management, documentation wiki, and project management. # # This file creates the `devstack` network. The main docker-compose.yml # references it as external so Caddy can reach these services. # # Fresh-install order: # docker compose -f portainer-compose.yml up -d # portainer_proxy network # docker compose -f dev-compose.yml up -d # devstack network + dev tools # dc up -d # application stack # # Apply changes: # powershell -File apply-compose.ps1 -DevStack services: # ============================================================ # GITEA (self-hosted Git) # ============================================================ gitea: container_name: gitea image: gitea/gitea:latest restart: unless-stopped networks: - devstack environment: - USER_UID=1000 - USER_GID=1000 - GITEA__database__DB_TYPE=postgres - GITEA__database__HOST=postgres_gitea:5432 - GITEA__database__NAME=gitea - GITEA__database__USER=gitea - GITEA__database__PASSWD=${GITEA_DB_PASSWORD} - GITEA__server__DOMAIN=gitea.ginnoir.com - GITEA__server__ROOT_URL=https://gitea.ginnoir.com - GITEA__server__SSH_DOMAIN=gitea.ginnoir.com - GITEA__server__SSH_PORT=2222 volumes: - /config/gitea:/data - /etc/localtime:/etc/localtime:ro ports: - "3030:3000" - "2222:22" depends_on: postgres_gitea: condition: service_healthy postgres_gitea: container_name: postgres_gitea image: postgres:16-alpine restart: unless-stopped networks: - devstack environment: POSTGRES_USER: gitea POSTGRES_PASSWORD: ${GITEA_DB_PASSWORD} POSTGRES_DB: gitea volumes: - gitea_db_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U gitea -d gitea"] interval: 10s timeout: 5s retries: 5 # ============================================================ # CODE-SERVER (VS Code in browser) # ============================================================ code-server: container_name: code_server image: lscr.io/linuxserver/code-server:latest restart: unless-stopped networks: - devstack environment: - PUID=1000 - PGID=1000 - TZ=${TZ} - PASSWORD=${CODE_SERVER_PASSWORD} - SUDO_PASSWORD=${CODE_SERVER_PASSWORD} - DEFAULT_WORKSPACE=/workspace volumes: - /config/code-server:/config - /home/ginnoir:/workspace ports: - "8443:8443" # ============================================================ # PRIVATE CONTAINER REGISTRY # ============================================================ registry: container_name: registry image: registry:2 restart: unless-stopped networks: - devstack volumes: - /config/registry:/var/lib/registry ports: - "5000:5000" # ============================================================ # DBX (Multi-database web UI) # https://github.com/t8y2/dbx # ============================================================ dbx: container_name: dbx image: t8y2/dbx:latest restart: unless-stopped networks: - devstack volumes: - dbx_data:/app/data ports: - "4224:4224" # ============================================================ # VAULT (Secret management) # One-time setup after first start: # ssh ginnoir@valhalla # docker exec -it vault vault operator init # vault operator unseal (three times with unseal keys) # ============================================================ vault: container_name: vault image: hashicorp/vault:latest restart: unless-stopped networks: - devstack cap_add: - IPC_LOCK environment: - VAULT_ADDR=http://0.0.0.0:8200 volumes: - /config/vault/config:/vault/config:ro - /config/vault/data:/vault/data - /config/vault/logs:/vault/logs command: vault server -config=/vault/config/vault.hcl ports: - "8200:8200" # ============================================================ # BOOKSTACK (Documentation / Wiki) # ============================================================ bookstack: container_name: bookstack image: lscr.io/linuxserver/bookstack:latest restart: unless-stopped networks: - devstack environment: - PUID=1000 - PGID=1000 - TZ=${TZ} - APP_URL=https://docs.ginnoir.com - DB_HOST=mariadb_bookstack - DB_PORT=3306 - DB_USER=bookstack - DB_PASS=${BOOKSTACK_DB_PASSWORD} - DB_DATABASE=bookstack volumes: - /config/bookstack:/config ports: - "6875:80" depends_on: mariadb_bookstack: condition: service_healthy mariadb_bookstack: container_name: mariadb_bookstack image: lscr.io/linuxserver/mariadb:latest restart: unless-stopped networks: - devstack environment: - PUID=1000 - PGID=1000 - TZ=${TZ} - MYSQL_ROOT_PASSWORD=${BOOKSTACK_DB_ROOT_PASSWORD} - MYSQL_DATABASE=bookstack - MYSQL_USER=bookstack - MYSQL_PASSWORD=${BOOKSTACK_DB_PASSWORD} volumes: - /config/bookstack-db:/config healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] interval: 10s timeout: 5s retries: 5 # ============================================================ # PLANE (Project management) # First start: log into https://plane.ginnoir.com and create a workspace. # Caddy routes /api/ and /auth/ to plane-api, everything else to plane-web. # ============================================================ plane-web: container_name: plane_web image: makeplane/plane-frontend:latest restart: unless-stopped networks: - devstack command: node web/server.js environment: - NEXT_PUBLIC_API_BASE_URL=https://plane.ginnoir.com plane-api: container_name: plane_api image: makeplane/plane-backend:latest restart: unless-stopped networks: - devstack command: ./bin/docker-entrypoint-api.sh environment: - SECRET_KEY=${PLANE_SECRET_KEY} - DEBUG=0 - DATABASE_URL=postgresql://plane:${PLANE_DB_PASSWORD}@postgres_plane:5432/plane - REDIS_URL=redis://redis_plane:6379/ - USE_MINIO=1 - AWS_REGION=us-east-1 - AWS_ACCESS_KEY_ID=${PLANE_MINIO_USER} - AWS_SECRET_ACCESS_KEY=${PLANE_MINIO_PASSWORD} - AWS_S3_ENDPOINT_URL=http://plane_minio:9000 - AWS_S3_BUCKET_NAME=uploads - WEB_URL=https://plane.ginnoir.com - CORS_ALLOWED_ORIGINS=https://plane.ginnoir.com - GUNICORN_WORKERS=2 depends_on: postgres_plane: condition: service_healthy redis_plane: condition: service_healthy plane-migrator: condition: service_completed_successfully plane-worker: container_name: plane_worker image: makeplane/plane-backend:latest restart: unless-stopped networks: - devstack command: ./bin/docker-entrypoint-worker.sh environment: - SECRET_KEY=${PLANE_SECRET_KEY} - DEBUG=0 - DATABASE_URL=postgresql://plane:${PLANE_DB_PASSWORD}@postgres_plane:5432/plane - REDIS_URL=redis://redis_plane:6379/ - USE_MINIO=1 - AWS_REGION=us-east-1 - AWS_ACCESS_KEY_ID=${PLANE_MINIO_USER} - AWS_SECRET_ACCESS_KEY=${PLANE_MINIO_PASSWORD} - AWS_S3_ENDPOINT_URL=http://plane_minio:9000 - AWS_S3_BUCKET_NAME=uploads depends_on: postgres_plane: condition: service_healthy redis_plane: condition: service_healthy plane-migrator: condition: service_completed_successfully plane-beat: container_name: plane_beat image: makeplane/plane-backend:latest restart: unless-stopped networks: - devstack command: ./bin/docker-entrypoint-beat.sh environment: - SECRET_KEY=${PLANE_SECRET_KEY} - DEBUG=0 - DATABASE_URL=postgresql://plane:${PLANE_DB_PASSWORD}@postgres_plane:5432/plane - REDIS_URL=redis://redis_plane:6379/ depends_on: postgres_plane: condition: service_healthy redis_plane: condition: service_healthy plane-migrator: condition: service_completed_successfully plane-migrator: container_name: plane_migrator image: makeplane/plane-backend:latest restart: "no" networks: - devstack command: ./bin/docker-entrypoint-migrator.sh environment: - SECRET_KEY=${PLANE_SECRET_KEY} - DATABASE_URL=postgresql://plane:${PLANE_DB_PASSWORD}@postgres_plane:5432/plane - REDIS_URL=redis://redis_plane:6379/ depends_on: postgres_plane: condition: service_healthy redis_plane: condition: service_healthy plane-create-bucket: container_name: plane_create_bucket image: minio/mc:latest restart: "no" networks: - devstack depends_on: plane-minio: condition: service_healthy entrypoint: > /bin/sh -c " until /usr/bin/mc alias set plane_minio http://plane_minio:9000 $$MINIO_USER $$MINIO_PASSWORD; do sleep 1; done; /usr/bin/mc mb plane_minio/uploads --ignore-existing; exit 0; " environment: - MINIO_USER=${PLANE_MINIO_USER} - MINIO_PASSWORD=${PLANE_MINIO_PASSWORD} postgres_plane: container_name: postgres_plane image: postgres:16-alpine restart: unless-stopped networks: - devstack environment: POSTGRES_USER: plane POSTGRES_PASSWORD: ${PLANE_DB_PASSWORD} POSTGRES_DB: plane volumes: - plane_db_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U plane -d plane"] interval: 10s timeout: 5s retries: 5 redis_plane: container_name: redis_plane image: redis:7-alpine restart: unless-stopped networks: - devstack command: --save 60 1 --loglevel warning volumes: - plane_redis_data:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 5 plane-minio: container_name: plane_minio image: minio/minio:latest restart: unless-stopped networks: - devstack command: server /data --console-address ":9001" environment: MINIO_ROOT_USER: ${PLANE_MINIO_USER} MINIO_ROOT_PASSWORD: ${PLANE_MINIO_PASSWORD} volumes: - plane_minio_data:/data healthcheck: test: ["CMD-SHELL", "curl -sf http://localhost:9000/minio/health/live || exit 1"] interval: 30s timeout: 20s retries: 3 start_period: 30s networks: devstack: name: devstack volumes: gitea_db_data: dbx_data: plane_db_data: plane_redis_data: plane_minio_data: