Files
picpeak/deploy/docker-stack.yml
T
paul 6c82958c79 Add complete frontend implementation and Docker deployment setup
- Implement React frontend with TypeScript and Tailwind CSS
- Add scrappbook.de-inspired UI design with photo galleries
- Implement authentication, photo viewing, and download features
- Add Docker Swarm configuration with Traefik reverse proxy
- Set up Drone CI/CD pipeline for automated deployments
- Add monitoring stack with Prometheus and Grafana
- Create comprehensive deployment documentation
- Add simple local development setup with docker-compose.local.yml

Features:
- Password-protected galleries with expiration warnings
- Responsive photo grid with lightbox viewer
- Bulk download functionality
- Hot reload development environment
- Email testing with Mailhog
- Production-ready deployment scripts

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-06 20:23:13 +02:00

265 lines
7.1 KiB
YAML

version: '3.8'
services:
backend:
image: ${REGISTRY_URL}/photo-sharing-backend:${VERSION:-latest}
networks:
- photo-sharing
- traefik-public
environment:
- NODE_ENV=production
- PORT=3000
- JWT_SECRET_FILE=/run/secrets/jwt_secret
- ADMIN_URL=${ADMIN_URL}
- FRONTEND_URL=${FRONTEND_URL}
- SMTP_HOST=${SMTP_HOST}
- SMTP_PORT=${SMTP_PORT}
- SMTP_SECURE=${SMTP_SECURE}
- SMTP_USER_FILE=/run/secrets/smtp_user
- SMTP_PASS_FILE=/run/secrets/smtp_pass
- EMAIL_FROM=${EMAIL_FROM}
- UMAMI_URL=${UMAMI_URL}
- UMAMI_WEBSITE_ID=${UMAMI_WEBSITE_ID}
- DB_HOST=db
- DB_PORT=5432
- DB_NAME=${DB_NAME:-photo_sharing}
- DB_USER_FILE=/run/secrets/db_user
- DB_PASSWORD_FILE=/run/secrets/db_password
secrets:
- jwt_secret
- smtp_user
- smtp_pass
- db_user
- db_password
volumes:
- photo-storage:/app/storage
- app-data:/app/data
- app-logs:/app/logs
deploy:
replicas: 3
update_config:
parallelism: 1
delay: 10s
failure_action: rollback
max_failure_ratio: 0.3
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
resources:
limits:
cpus: '1'
memory: 512M
reservations:
cpus: '0.25'
memory: 128M
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik-public"
- "traefik.constraint-label=traefik-public"
- "traefik.http.routers.backend.rule=Host(`${BACKEND_HOST}`) && PathPrefix(`/api`)"
- "traefik.http.routers.backend.entrypoints=https"
- "traefik.http.routers.backend.tls=true"
- "traefik.http.routers.backend.tls.certresolver=letsencrypt"
- "traefik.http.services.backend.loadbalancer.server.port=3000"
- "traefik.http.services.backend.loadbalancer.healthcheck.path=/api/health"
- "traefik.http.services.backend.loadbalancer.healthcheck.interval=10s"
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3000/api/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
frontend:
image: ${REGISTRY_URL}/photo-sharing-frontend:${VERSION:-latest}
networks:
- photo-sharing
- traefik-public
deploy:
replicas: 2
update_config:
parallelism: 1
delay: 10s
failure_action: rollback
restart_policy:
condition: on-failure
resources:
limits:
cpus: '0.5'
memory: 256M
reservations:
cpus: '0.1'
memory: 64M
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik-public"
- "traefik.constraint-label=traefik-public"
- "traefik.http.routers.frontend.rule=Host(`${FRONTEND_HOST}`)"
- "traefik.http.routers.frontend.entrypoints=https"
- "traefik.http.routers.frontend.tls=true"
- "traefik.http.routers.frontend.tls.certresolver=letsencrypt"
- "traefik.http.services.frontend.loadbalancer.server.port=80"
- "traefik.http.middlewares.frontend-compress.compress=true"
- "traefik.http.routers.frontend.middlewares=frontend-compress"
db:
image: postgres:14-alpine
networks:
- photo-sharing
environment:
- POSTGRES_USER_FILE=/run/secrets/db_user
- POSTGRES_PASSWORD_FILE=/run/secrets/db_password
- POSTGRES_DB=${DB_NAME:-photo_sharing}
secrets:
- db_user
- db_password
volumes:
- postgres-data:/var/lib/postgresql/data
deploy:
placement:
constraints:
- node.labels.db == true
restart_policy:
condition: on-failure
resources:
limits:
cpus: '2'
memory: 1G
reservations:
cpus: '0.5'
memory: 256M
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
# Background workers as separate services for better control
email-worker:
image: ${REGISTRY_URL}/photo-sharing-backend:${VERSION:-latest}
command: ["node", "src/services/emailService.js"]
networks:
- photo-sharing
environment:
- NODE_ENV=production
- JWT_SECRET_FILE=/run/secrets/jwt_secret
- SMTP_HOST=${SMTP_HOST}
- SMTP_PORT=${SMTP_PORT}
- SMTP_SECURE=${SMTP_SECURE}
- SMTP_USER_FILE=/run/secrets/smtp_user
- SMTP_PASS_FILE=/run/secrets/smtp_pass
- EMAIL_FROM=${EMAIL_FROM}
secrets:
- jwt_secret
- smtp_user
- smtp_pass
volumes:
- app-data:/app/data
- app-logs:/app/logs
deploy:
replicas: 1
restart_policy:
condition: on-failure
delay: 5s
resources:
limits:
cpus: '0.5'
memory: 256M
expiration-checker:
image: ${REGISTRY_URL}/photo-sharing-backend:${VERSION:-latest}
command: ["node", "src/services/expirationChecker.js"]
networks:
- photo-sharing
environment:
- NODE_ENV=production
volumes:
- photo-storage:/app/storage
- app-data:/app/data
- app-logs:/app/logs
deploy:
replicas: 1
restart_policy:
condition: on-failure
delay: 5s
resources:
limits:
cpus: '0.5'
memory: 256M
archive-worker:
image: ${REGISTRY_URL}/photo-sharing-backend:${VERSION:-latest}
command: ["node", "src/services/archiveService.js"]
networks:
- photo-sharing
environment:
- NODE_ENV=production
volumes:
- photo-storage:/app/storage
- app-data:/app/data
- app-logs:/app/logs
deploy:
replicas: 1
restart_policy:
condition: on-failure
delay: 5s
resources:
limits:
cpus: '1'
memory: 512M
# Umami Analytics
umami:
image: ghcr.io/umami-software/umami:postgresql-latest
networks:
- photo-sharing
- traefik-public
environment:
DATABASE_URL: postgresql://umami:${UMAMI_DB_PASSWORD}@db:5432/umami
DATABASE_TYPE: postgresql
HASH_SALT: ${UMAMI_HASH_SALT}
depends_on:
- db
deploy:
replicas: 1
restart_policy:
condition: on-failure
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik-public"
- "traefik.constraint-label=traefik-public"
- "traefik.http.routers.umami.rule=Host(`${UMAMI_HOST}`)"
- "traefik.http.routers.umami.entrypoints=https"
- "traefik.http.routers.umami.tls=true"
- "traefik.http.routers.umami.tls.certresolver=letsencrypt"
- "traefik.http.services.umami.loadbalancer.server.port=3000"
networks:
photo-sharing:
driver: overlay
attachable: true
traefik-public:
external: true
volumes:
postgres-data:
driver: local
photo-storage:
driver: local
app-data:
driver: local
app-logs:
driver: local
secrets:
jwt_secret:
external: true
smtp_user:
external: true
smtp_pass:
external: true
db_user:
external: true
db_password:
external: true