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>
This commit is contained in:
@@ -0,0 +1,265 @@
|
||||
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
|
||||
Reference in New Issue
Block a user