Files
jmp-stack/docker-compose.yaml
2026-01-10 23:34:39 +01:00

608 lines
17 KiB
YAML

networks:
proxy_net:
name: proxy_net
driver: bridge
gitea_backend:
internal: true
bookstack_backend:
internal: true
vikunja_backend:
internal: true
services:
# --- BACKUP ---
backup:
image: offen/docker-volume-backup:v2
container_name: backup
restart: unless-stopped
env_file: ./backup.env
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
# Backup sources (read-only)
- ./gitea/data:/backup/gitea-data:ro
- ./gitea/db:/backup/gitea-db:ro
- ./bookstack/app:/backup/bookstack-app:ro
- ./bookstack/db:/backup/bookstack-db:ro
- ./vikunja/files:/backup/vikunja-files:ro
- ./vikunja/db:/backup/vikunja-db:ro
- ./radicale/data:/backup/radicale-data:ro
- ./vaultwarden/data:/backup/vaultwarden-data:ro
- ./homepage:/backup/homepage:ro
- ./actual-data:/backup/actual:ro
# Local backup archive
- ./backups:/archive
deploy:
resources:
limits:
cpus: '2'
memory: 2G
reservations:
cpus: '1'
memory: 1G
logging:
driver: "json-file"
options:
max-size: "100m"
max-file: "5"
labels:
- "homepage.group=Infrastructure"
- "homepage.name=Backup"
- "homepage.icon=duplicati.png"
- "homepage.description=Volume Backup Service"
traefik:
image: traefik:v3.6.6
container_name: traefik
restart: unless-stopped
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./traefik.yml:/etc/traefik/traefik.yml
- ./acme.json:/acme.json
- ./logs/traefik:/var/log/traefik # Logs for Fail2Ban
networks:
- proxy_net
deploy:
resources:
limits:
cpus: '2'
memory: 512M
reservations:
cpus: '0.5'
memory: 256M
healthcheck:
test: ["CMD", "traefik", "healthcheck", "--ping"]
interval: 30s
timeout: 10s
retries: 3
start_period: 20s
logging:
driver: "json-file"
options:
max-size: "100m"
max-file: "5"
labels:
- "traefik.enable=true"
- "traefik.http.routers.dashboard.rule=Host(`traefik.${DOMAIN}`)"
- "traefik.http.routers.dashboard.service=api@internal"
- "traefik.http.routers.dashboard.middlewares=auth"
# IMPORTANT: Replace with actual htpasswd hash or set via environment variable
- "traefik.http.middlewares.auth.basicauth.users=${TRAEFIK_BASICAUTH_USERS}"
# Homepage Monitoring
- "homepage.group=Infrastructure"
- "homepage.name=Traefik"
- "homepage.icon=traefik.png"
- "homepage.href=https://traefik.${DOMAIN}"
homepage:
image: ghcr.io/gethomepage/homepage:latest
container_name: homepage
restart: unless-stopped
volumes:
- ./homepage:/app/config
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
HOMEPAGE_ALLOWED_HOSTS: "*"
PUID: ${PUID}
PGID: 988
networks:
- proxy_net
deploy:
resources:
limits:
cpus: '1'
memory: 256M
reservations:
cpus: '0.25'
memory: 128M
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3000/"]
interval: 30s
timeout: 10s
retries: 3
start_period: 20s
logging:
driver: "json-file"
options:
max-size: "50m"
max-file: "3"
labels:
- "traefik.enable=true"
- "traefik.http.routers.homepage.rule=Host(`homepage.${DOMAIN}`)"
- "traefik.http.routers.homepage.entrypoints=websecure"
- "traefik.http.routers.homepage.tls.certresolver=letsencrypt"
- "traefik.http.services.homepage.loadbalancer.server.port=3000"
- "traefik.docker.network=proxy_net"
# --- GITEA ---
gitea-db:
image: postgres:14-alpine
container_name: gitea-db
restart: always
environment:
POSTGRES_USER: ${POSTGRES_USER_GITEA}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD_GITEA}
POSTGRES_DB: ${POSTGRES_NAME_GITEA}
networks:
- gitea_backend
volumes:
- ./gitea/db:/var/lib/postgresql/data
deploy:
resources:
limits:
cpus: '2'
memory: 1G
reservations:
cpus: '1'
memory: 512M
healthcheck:
test: ["CMD-SHELL", "pg_isready -h localhost -U ${POSTGRES_USER_GITEA}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
logging:
driver: "json-file"
options:
max-size: "50m"
max-file: "3"
labels:
- "docker-volume-backup.stop-during-backup=true"
gitea:
image: gitea/gitea:latest
container_name: gitea
restart: unless-stopped
environment:
- USER_UID=${PUID}
- USER_GID=${PGID}
- GITEA__database__DB_TYPE=postgres
- GITEA__database__HOST=${POSTGRES_HOST_GITEA}
- GITEA__database__NAME=${POSTGRES_NAME_GITEA}
- GITEA__database__USER=${POSTGRES_USER_GITEA}
- GITEA__database__PASSWD=${POSTGRES_PASSWORD_GITEA}
- GITEA__server__DOMAIN=gitea.${DOMAIN}
- GITEA__server__SSH_PORT=2222
- GITEA__server__ROOT_URL=https://gitea.${DOMAIN}/
- GITEA__actions__ENABLED=true
- GITEA__service__DISABLE_REGISTRATION=true
ports:
- "2222:22"
volumes:
- ./gitea/data:/data
networks:
- proxy_net
- gitea_backend
depends_on:
gitea-db:
condition: service_healthy
deploy:
resources:
limits:
cpus: '2'
memory: 1G
reservations:
cpus: '1'
memory: 512M
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
logging:
driver: "json-file"
options:
max-size: "50m"
max-file: "3"
labels:
- "traefik.enable=true"
- "traefik.http.routers.gitea.rule=Host(`gitea.${DOMAIN}`)"
- "traefik.http.routers.gitea.entrypoints=websecure"
- "traefik.http.routers.gitea.tls.certresolver=letsencrypt"
- "traefik.http.services.gitea.loadbalancer.server.port=3000"
- "homepage.group=Dev"
- "homepage.name=Gitea"
- "homepage.icon=gitea.png"
- "homepage.href=https://gitea.${DOMAIN}"
- "docker-volume-backup.stop-during-backup=true"
gitea-runner:
image: gitea/act_runner:latest
container_name: gitea-runner
restart: unless-stopped
environment:
CONFIG_FILE: /config.yml
GITEA_INSTANCE_URL: "${GITEA_INSTANCE_URL}"
GITEA_RUNNER_REGISTRATION_TOKEN: "${GITEA_RUNNER_REGISTRATION_TOKEN}"
GITEA_RUNNER_NAME: "${GITEA_RUNNER_NAME}"
GITEA_RUNNER_LABELS: "${GITEA_RUNNER_LABELS}"
volumes:
- ./runner-data:/data
- ./config.yml:/config.yml
- /var/run/docker.sock:/var/run/docker.sock
networks:
- proxy_net
depends_on:
gitea:
condition: service_healthy
deploy:
resources:
limits:
cpus: '4'
memory: 2G
reservations:
cpus: '2'
memory: 1G
logging:
driver: "json-file"
options:
max-size: "50m"
max-file: "3"
labels:
- "homepage.group=Dev"
- "homepage.icon=gitea.png"
- "homepage.name=Gitea Runner"
- "homepage.description=CI/CD Worker"
# --- BOOKSTACK ---
bookstack-db:
image: lscr.io/linuxserver/mariadb:latest
container_name: bookstack-db
restart: always
environment:
- PUID=${PUID}
- PGID=${PGID}
- TZ=${MYSQL_TZ_BOOKSTACK}
- MYSQL_ROOT_PASSWORD=${MYSQL_PASSWORD_BOOKSTACK}
- MYSQL_DATABASE=${MYSQL_NAME_BOOKSTACK}
- MYSQL_USER=${MYSQL_USER_BOOKSTACK}
- MYSQL_PASSWORD=${MYSQL_PASSWORD_BOOKSTACK}
networks:
- bookstack_backend
volumes:
- ./bookstack/db:/config
deploy:
resources:
limits:
cpus: '2'
memory: 1G
reservations:
cpus: '1'
memory: 512M
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_PASSWORD_BOOKSTACK}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
logging:
driver: "json-file"
options:
max-size: "50m"
max-file: "3"
labels:
- "docker-volume-backup.stop-during-backup=true"
bookstack:
image: lscr.io/linuxserver/bookstack:latest
container_name: bookstack
restart: unless-stopped
environment:
- PUID=${PUID}
- PGID=${PGID}
- DB_HOST=${MYSQL_HOST_BOOKSTACK}
- DB_PORT=${MYSQL_PORT_BOOKSTACK}
- DB_USERNAME=${MYSQL_USER_BOOKSTACK}
- DB_PASSWORD=${MYSQL_PASSWORD_BOOKSTACK}
- DB_DATABASE=${MYSQL_NAME_BOOKSTACK}
- APP_URL=${BOOKSTACK_APP_URL}
- APP_ASSET_URL=${BOOKSTACK_APP_URL}
- APP_KEY=${BOOKSTACK_APP_KEY}
volumes:
- ./bookstack/app:/config
networks:
- proxy_net
- bookstack_backend
depends_on:
bookstack-db:
condition: service_healthy
deploy:
resources:
limits:
cpus: '2'
memory: 512M
reservations:
cpus: '1'
memory: 256M
# Note: Bookstack uses LS.IO init system with process supervision
# Health check disabled - container runs well under init supervision
logging:
driver: "json-file"
options:
max-size: "50m"
max-file: "3"
labels:
- "traefik.enable=true"
- "traefik.http.routers.bookstack.rule=Host(`bookstack.${DOMAIN}`)"
- "traefik.http.routers.bookstack.entrypoints=websecure"
- "traefik.http.routers.bookstack.tls.certresolver=letsencrypt"
- "traefik.http.services.bookstack.loadbalancer.server.port=80"
- "traefik.docker.network=proxy_net"
- "homepage.group=Dev"
- "homepage.name=Bookstack"
- "homepage.icon=bookstack.png"
- "homepage.href=https://bookstack.${DOMAIN}"
- "docker-volume-backup.stop-during-backup=true"
# --- WEBSITE ---
website:
image: gitea.jmpgames.it/admin/website:latest
container_name: website
profiles: ["after"]
restart: unless-stopped
networks:
- proxy_net
deploy:
resources:
limits:
cpus: '1'
memory: 256M
reservations:
cpus: '0.5'
memory: 128M
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:80/"]
interval: 30s
timeout: 10s
retries: 3
start_period: 20s
logging:
driver: "json-file"
options:
max-size: "50m"
max-file: "3"
labels:
- "traefik.enable=true"
- "traefik.http.routers.website.rule=Host(`${DOMAIN}`) || Host(`www.${DOMAIN}`)"
- "traefik.http.routers.website.entrypoints=websecure"
- "traefik.http.routers.website.tls.certresolver=letsencrypt"
- "traefik.http.services.website.loadbalancer.server.port=80"
- "homepage.group=Public"
- "homepage.name=Website"
- "homepage.href=https://${DOMAIN}"
# --- VIKUNJA ---
vikunja-db:
image: postgres:17-alpine
container_name: vikunja-db
restart: always
environment:
POSTGRES_USER: ${POSTGRES_USER_VIKUNJA}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD_VIKUNJA}
POSTGRES_DB: ${POSTGRES_NAME_VIKUNJA}
networks:
- vikunja_backend
volumes:
- ./vikunja/db:/var/lib/postgresql/data
deploy:
resources:
limits:
cpus: '2'
memory: 1G
reservations:
cpus: '1'
memory: 512M
healthcheck:
test: ["CMD-SHELL", "pg_isready -h localhost -U ${POSTGRES_USER_VIKUNJA}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
logging:
driver: "json-file"
options:
max-size: "50m"
max-file: "3"
labels:
- "docker-volume-backup.stop-during-backup=true"
vikunja:
image: vikunja/vikunja:latest
container_name: vikunja
restart: unless-stopped
user: "${PUID}:${PGID}"
environment:
VIKUNJA_SERVICE_PUBLICURL: https://vikunja.${DOMAIN}/
VIKUNJA_SERVICE_JWTSECRET: ${VIKUNJA_JWT_SECRET}
VIKUNJA_DATABASE_TYPE: postgres
VIKUNJA_DATABASE_HOST: vikunja-db
VIKUNJA_DATABASE_DATABASE: ${POSTGRES_NAME_VIKUNJA}
VIKUNJA_DATABASE_USER: ${POSTGRES_USER_VIKUNJA}
VIKUNJA_DATABASE_PASSWORD: ${POSTGRES_PASSWORD_VIKUNJA}
VIKUNJA_SERVICE_ENABLEREGISTRATION: false
volumes:
- ./vikunja/files:/app/vikunja/files
networks:
- proxy_net
- vikunja_backend
depends_on:
vikunja-db:
condition: service_healthy
deploy:
resources:
limits:
cpus: '2'
memory: 512M
reservations:
cpus: '1'
memory: 256M
# Note: Vikunja image doesn't include shell/curl, relying on process supervision instead
# healthcheck disabled - container runs well without it
logging:
driver: "json-file"
options:
max-size: "50m"
max-file: "3"
labels:
- "traefik.enable=true"
- "traefik.http.routers.vikunja.rule=Host(`vikunja.${DOMAIN}`)"
- "traefik.http.routers.vikunja.entrypoints=websecure"
- "traefik.http.routers.vikunja.tls.certresolver=letsencrypt"
- "traefik.http.services.vikunja.loadbalancer.server.port=3456"
- "traefik.docker.network=proxy_net"
- "homepage.group=Dev"
- "homepage.name=Vikunja"
- "homepage.icon=vikunja.png"
- "homepage.href=https://vikunja.${DOMAIN}"
- "docker-volume-backup.stop-during-backup=true"
# --- VAULTWARDEN ---
vaultwarden:
image: vaultwarden/server:latest
container_name: vaultwarden
restart: unless-stopped
environment:
DOMAIN: https://vault.${DOMAIN}
SIGNUPS_ALLOWED: ${VAULTWARDEN_SIGNUPS_ALLOWED:-false}
ADMIN_TOKEN: ${VAULTWARDEN_ADMIN_TOKEN}
volumes:
- ./vaultwarden/data:/data
networks:
- proxy_net
deploy:
resources:
limits:
cpus: '1'
memory: 512M
reservations:
cpus: '0.5'
memory: 256M
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80/alive"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
logging:
driver: "json-file"
options:
max-size: "50m"
max-file: "3"
labels:
- "traefik.enable=true"
- "traefik.http.routers.vaultwarden.rule=Host(`vault.${DOMAIN}`)"
- "traefik.http.routers.vaultwarden.entrypoints=websecure"
- "traefik.http.routers.vaultwarden.tls.certresolver=letsencrypt"
- "traefik.http.services.vaultwarden.loadbalancer.server.port=80"
- "homepage.group=Tools"
- "homepage.name=Vaultwarden"
- "homepage.icon=vaultwarden.png"
- "homepage.href=https://vault.${DOMAIN}"
- "docker-volume-backup.stop-during-backup=true"
# --- RADICALE ---
radicale:
image: tomsquest/docker-radicale:latest
container_name: radicale
restart: unless-stopped
volumes:
- ./radicale/data:/data
- ./radicale/config:/config:ro
networks:
- proxy_net
deploy:
resources:
limits:
cpus: '1'
memory: 256M
reservations:
cpus: '0.5'
memory: 128M
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5232/"]
interval: 30s
timeout: 10s
retries: 3
start_period: 20s
logging:
driver: "json-file"
options:
max-size: "50m"
max-file: "3"
labels:
- "traefik.enable=true"
- "traefik.http.routers.radicale.rule=Host(`radicale.${DOMAIN}`)"
- "traefik.http.routers.radicale.entrypoints=websecure"
- "traefik.http.routers.radicale.tls.certresolver=letsencrypt"
- "traefik.http.services.radicale.loadbalancer.server.port=5232"
- "homepage.group=Tools"
- "homepage.name=Radicale"
- "homepage.icon=radicale.png"
- "homepage.href=https://radicale.${DOMAIN}"
- "docker-volume-backup.stop-during-backup=true"
# --- Actual Budget ---
actual_server:
image: docker.io/actualbudget/actual-server:latest
container_name: actual_server
volumes:
- ./actual-data:/data
networks:
- proxy_net
restart: unless-stopped
deploy:
resources:
limits:
cpus: '1'
memory: 512M
reservations:
cpus: '0.5'
memory: 256M
healthcheck:
test: ['CMD-SHELL', 'node src/scripts/health-check.js']
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
logging:
driver: "json-file"
options:
max-size: "50m"
max-file: "3"
labels:
- "traefik.enable=true"
- "traefik.http.routers.actual.rule=Host(`actual.${DOMAIN}`)"
- "traefik.http.routers.actual.entrypoints=websecure"
- "traefik.http.routers.actual.tls.certresolver=letsencrypt"
- "traefik.http.services.actual.loadbalancer.server.port=5006"
- "homepage.group=Tools"
- "homepage.name=Actual"
- "homepage.icon=actual.png"
- "homepage.href=https://actual.${DOMAIN}"
- "docker-volume-backup.stop-during-backup=true"