$ services: web: image: wger/server:2.4 container_name: wger -weight: 500;">restart: unless-stopped environment: # Security — CHANGE THESE SECRET_KEY: "generate-a-random-50-char-string-here" # CHANGE THIS SIGNING_KEY: "generate-a-different-random-string" # CHANGE THIS SITE_URL: "http://localhost" # CHANGE to your domain # Database DJANGO_DB_ENGINE: django.db.backends.postgresql DJANGO_DB_DATABASE: wger DJANGO_DB_USER: wger DJANGO_DB_PASSWORD: change-this-db-password # CHANGE THIS DJANGO_DB_HOST: db DJANGO_DB_PORT: "5432" DJANGO_PERFORM_MIGRATIONS: "True" # Cache DJANGO_CACHE_BACKEND: django_redis.cache.RedisCache DJANGO_CACHE_LOCATION: redis://cache:6379/1 DJANGO_CACHE_TIMEOUT: "1296000" # Celery USE_CELERY: "True" CELERY_BROKER: redis://cache:6379/2 CELERY_BACKEND: redis://cache:6379/2 # App settings WGER_INSTANCE: https://wger.de ALLOW_REGISTRATION: "True" ALLOW_GUEST_USERS: "True" TIME_ZONE: UTC WGER_USE_GUNICORN: "True" volumes: - wger-static:/home/wger/static - wger-media:/home/wger/media depends_on: db: condition: service_healthy cache: condition: service_healthy healthcheck: test: ["CMD", "-weight: 500;">wget", "-q", "--no-check-certificate", "--spider", "http://localhost:8000"] interval: 30s timeout: 10s retries: 5 networks: - wger nginx: image: nginx:stable container_name: wger-nginx -weight: 500;">restart: unless-stopped ports: - "80:80" volumes: - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro - wger-static:/wger/static:ro - wger-media:/wger/media:ro depends_on: - web networks: - wger db: image: postgres:15-alpine container_name: wger-db -weight: 500;">restart: unless-stopped environment: POSTGRES_DB: wger POSTGRES_USER: wger POSTGRES_PASSWORD: change-this-db-password # Must match DJANGO_DB_PASSWORD volumes: - wger-postgres:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U wger"] interval: 10s timeout: 5s retries: 5 networks: - wger cache: image: redis:7-alpine container_name: wger-cache -weight: 500;">restart: unless-stopped volumes: - wger-redis:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 5 networks: - wger celery_worker: image: wger/server:2.4 container_name: wger-celery-worker -weight: 500;">restart: unless-stopped command: /-weight: 500;">start-worker environment: DJANGO_DB_ENGINE: django.db.backends.postgresql DJANGO_DB_DATABASE: wger DJANGO_DB_USER: wger DJANGO_DB_PASSWORD: change-this-db-password DJANGO_DB_HOST: db DJANGO_DB_PORT: "5432" DJANGO_CACHE_BACKEND: django_redis.cache.RedisCache DJANGO_CACHE_LOCATION: redis://cache:6379/1 USE_CELERY: "True" CELERY_BROKER: redis://cache:6379/2 CELERY_BACKEND: redis://cache:6379/2 CELERY_WORKER_CONCURRENCY: "2" WGER_INSTANCE: https://wger.de volumes: - wger-media:/home/wger/media depends_on: web: condition: service_healthy networks: - wger celery_beat: image: wger/server:2.4 container_name: wger-celery-beat -weight: 500;">restart: unless-stopped command: /-weight: 500;">start-beat environment: DJANGO_DB_ENGINE: django.db.backends.postgresql DJANGO_DB_DATABASE: wger DJANGO_DB_USER: wger DJANGO_DB_PASSWORD: change-this-db-password DJANGO_DB_HOST: db DJANGO_DB_PORT: "5432" USE_CELERY: "True" CELERY_BROKER: redis://cache:6379/2 CELERY_BACKEND: redis://cache:6379/2 volumes: - wger-beat:/home/wger/beat depends_on: celery_worker: condition: service_healthy networks: - wger volumes: wger-static: wger-media: wger-postgres: wger-redis: wger-beat: networks: wger: driver: bridge
services: web: image: wger/server:2.4 container_name: wger -weight: 500;">restart: unless-stopped environment: # Security — CHANGE THESE SECRET_KEY: "generate-a-random-50-char-string-here" # CHANGE THIS SIGNING_KEY: "generate-a-different-random-string" # CHANGE THIS SITE_URL: "http://localhost" # CHANGE to your domain # Database DJANGO_DB_ENGINE: django.db.backends.postgresql DJANGO_DB_DATABASE: wger DJANGO_DB_USER: wger DJANGO_DB_PASSWORD: change-this-db-password # CHANGE THIS DJANGO_DB_HOST: db DJANGO_DB_PORT: "5432" DJANGO_PERFORM_MIGRATIONS: "True" # Cache DJANGO_CACHE_BACKEND: django_redis.cache.RedisCache DJANGO_CACHE_LOCATION: redis://cache:6379/1 DJANGO_CACHE_TIMEOUT: "1296000" # Celery USE_CELERY: "True" CELERY_BROKER: redis://cache:6379/2 CELERY_BACKEND: redis://cache:6379/2 # App settings WGER_INSTANCE: https://wger.de ALLOW_REGISTRATION: "True" ALLOW_GUEST_USERS: "True" TIME_ZONE: UTC WGER_USE_GUNICORN: "True" volumes: - wger-static:/home/wger/static - wger-media:/home/wger/media depends_on: db: condition: service_healthy cache: condition: service_healthy healthcheck: test: ["CMD", "-weight: 500;">wget", "-q", "--no-check-certificate", "--spider", "http://localhost:8000"] interval: 30s timeout: 10s retries: 5 networks: - wger nginx: image: nginx:stable container_name: wger-nginx -weight: 500;">restart: unless-stopped ports: - "80:80" volumes: - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro - wger-static:/wger/static:ro - wger-media:/wger/media:ro depends_on: - web networks: - wger db: image: postgres:15-alpine container_name: wger-db -weight: 500;">restart: unless-stopped environment: POSTGRES_DB: wger POSTGRES_USER: wger POSTGRES_PASSWORD: change-this-db-password # Must match DJANGO_DB_PASSWORD volumes: - wger-postgres:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U wger"] interval: 10s timeout: 5s retries: 5 networks: - wger cache: image: redis:7-alpine container_name: wger-cache -weight: 500;">restart: unless-stopped volumes: - wger-redis:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 5 networks: - wger celery_worker: image: wger/server:2.4 container_name: wger-celery-worker -weight: 500;">restart: unless-stopped command: /-weight: 500;">start-worker environment: DJANGO_DB_ENGINE: django.db.backends.postgresql DJANGO_DB_DATABASE: wger DJANGO_DB_USER: wger DJANGO_DB_PASSWORD: change-this-db-password DJANGO_DB_HOST: db DJANGO_DB_PORT: "5432" DJANGO_CACHE_BACKEND: django_redis.cache.RedisCache DJANGO_CACHE_LOCATION: redis://cache:6379/1 USE_CELERY: "True" CELERY_BROKER: redis://cache:6379/2 CELERY_BACKEND: redis://cache:6379/2 CELERY_WORKER_CONCURRENCY: "2" WGER_INSTANCE: https://wger.de volumes: - wger-media:/home/wger/media depends_on: web: condition: service_healthy networks: - wger celery_beat: image: wger/server:2.4 container_name: wger-celery-beat -weight: 500;">restart: unless-stopped command: /-weight: 500;">start-beat environment: DJANGO_DB_ENGINE: django.db.backends.postgresql DJANGO_DB_DATABASE: wger DJANGO_DB_USER: wger DJANGO_DB_PASSWORD: change-this-db-password DJANGO_DB_HOST: db DJANGO_DB_PORT: "5432" USE_CELERY: "True" CELERY_BROKER: redis://cache:6379/2 CELERY_BACKEND: redis://cache:6379/2 volumes: - wger-beat:/home/wger/beat depends_on: celery_worker: condition: service_healthy networks: - wger volumes: wger-static: wger-media: wger-postgres: wger-redis: wger-beat: networks: wger: driver: bridge
services: web: image: wger/server:2.4 container_name: wger -weight: 500;">restart: unless-stopped environment: # Security — CHANGE THESE SECRET_KEY: "generate-a-random-50-char-string-here" # CHANGE THIS SIGNING_KEY: "generate-a-different-random-string" # CHANGE THIS SITE_URL: "http://localhost" # CHANGE to your domain # Database DJANGO_DB_ENGINE: django.db.backends.postgresql DJANGO_DB_DATABASE: wger DJANGO_DB_USER: wger DJANGO_DB_PASSWORD: change-this-db-password # CHANGE THIS DJANGO_DB_HOST: db DJANGO_DB_PORT: "5432" DJANGO_PERFORM_MIGRATIONS: "True" # Cache DJANGO_CACHE_BACKEND: django_redis.cache.RedisCache DJANGO_CACHE_LOCATION: redis://cache:6379/1 DJANGO_CACHE_TIMEOUT: "1296000" # Celery USE_CELERY: "True" CELERY_BROKER: redis://cache:6379/2 CELERY_BACKEND: redis://cache:6379/2 # App settings WGER_INSTANCE: https://wger.de ALLOW_REGISTRATION: "True" ALLOW_GUEST_USERS: "True" TIME_ZONE: UTC WGER_USE_GUNICORN: "True" volumes: - wger-static:/home/wger/static - wger-media:/home/wger/media depends_on: db: condition: service_healthy cache: condition: service_healthy healthcheck: test: ["CMD", "-weight: 500;">wget", "-q", "--no-check-certificate", "--spider", "http://localhost:8000"] interval: 30s timeout: 10s retries: 5 networks: - wger nginx: image: nginx:stable container_name: wger-nginx -weight: 500;">restart: unless-stopped ports: - "80:80" volumes: - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro - wger-static:/wger/static:ro - wger-media:/wger/media:ro depends_on: - web networks: - wger db: image: postgres:15-alpine container_name: wger-db -weight: 500;">restart: unless-stopped environment: POSTGRES_DB: wger POSTGRES_USER: wger POSTGRES_PASSWORD: change-this-db-password # Must match DJANGO_DB_PASSWORD volumes: - wger-postgres:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U wger"] interval: 10s timeout: 5s retries: 5 networks: - wger cache: image: redis:7-alpine container_name: wger-cache -weight: 500;">restart: unless-stopped volumes: - wger-redis:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 5 networks: - wger celery_worker: image: wger/server:2.4 container_name: wger-celery-worker -weight: 500;">restart: unless-stopped command: /-weight: 500;">start-worker environment: DJANGO_DB_ENGINE: django.db.backends.postgresql DJANGO_DB_DATABASE: wger DJANGO_DB_USER: wger DJANGO_DB_PASSWORD: change-this-db-password DJANGO_DB_HOST: db DJANGO_DB_PORT: "5432" DJANGO_CACHE_BACKEND: django_redis.cache.RedisCache DJANGO_CACHE_LOCATION: redis://cache:6379/1 USE_CELERY: "True" CELERY_BROKER: redis://cache:6379/2 CELERY_BACKEND: redis://cache:6379/2 CELERY_WORKER_CONCURRENCY: "2" WGER_INSTANCE: https://wger.de volumes: - wger-media:/home/wger/media depends_on: web: condition: service_healthy networks: - wger celery_beat: image: wger/server:2.4 container_name: wger-celery-beat -weight: 500;">restart: unless-stopped command: /-weight: 500;">start-beat environment: DJANGO_DB_ENGINE: django.db.backends.postgresql DJANGO_DB_DATABASE: wger DJANGO_DB_USER: wger DJANGO_DB_PASSWORD: change-this-db-password DJANGO_DB_HOST: db DJANGO_DB_PORT: "5432" USE_CELERY: "True" CELERY_BROKER: redis://cache:6379/2 CELERY_BACKEND: redis://cache:6379/2 volumes: - wger-beat:/home/wger/beat depends_on: celery_worker: condition: service_healthy networks: - wger volumes: wger-static: wger-media: wger-postgres: wger-redis: wger-beat: networks: wger: driver: bridge
upstream wger { server web:8000;
} server { listen 80; server_name _; location /static/ { alias /wger/static/; } location /media/ { alias /wger/media/; } location / { proxy_pass http://wger; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_redirect off; }
}
upstream wger { server web:8000;
} server { listen 80; server_name _; location /static/ { alias /wger/static/; } location /media/ { alias /wger/media/; } location / { proxy_pass http://wger; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_redirect off; }
}
upstream wger { server web:8000;
} server { listen 80; server_name _; location /static/ { alias /wger/static/; } location /media/ { alias /wger/media/; } location / { proxy_pass http://wger; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_redirect off; }
}
-weight: 500;">docker compose up -d
-weight: 500;">docker compose up -d
-weight: 500;">docker compose up -d
X_FORWARDED_PROTO_HEADER_SET: "True"
CSRF_TRUSTED_ORIGINS: "https://fitness.example.com"
X_FORWARDED_PROTO_HEADER_SET: "True"
CSRF_TRUSTED_ORIGINS: "https://fitness.example.com"
X_FORWARDED_PROTO_HEADER_SET: "True"
CSRF_TRUSTED_ORIGINS: "https://fitness.example.com"
# Database
-weight: 500;">docker exec wger-db pg_dump -U wger wger > wger-backup.sql # Media files (exercise images, user uploads)
-weight: 500;">docker run --rm -v wger-media:/data -v $(pwd):/backup alpine tar czf /backup/wger-media.tar.gz /data
# Database
-weight: 500;">docker exec wger-db pg_dump -U wger wger > wger-backup.sql # Media files (exercise images, user uploads)
-weight: 500;">docker run --rm -v wger-media:/data -v $(pwd):/backup alpine tar czf /backup/wger-media.tar.gz /data
# Database
-weight: 500;">docker exec wger-db pg_dump -U wger wger > wger-backup.sql # Media files (exercise images, user uploads)
-weight: 500;">docker run --rm -v wger-media:/data -v $(pwd):/backup alpine tar czf /backup/wger-media.tar.gz /data - A Linux server (Ubuntu 22.04+ recommended)
- Docker and Docker Compose installed (guide)
- 2 GB of RAM minimum (PostgreSQL + Redis + Celery workers)
- 10 GB of free disk space (grows with exercise images/videos)
- A domain name (optional, for remote access) - Access wger at http://your-server-ip
- Register an account (the first account has admin privileges)
- Go to Settings to configure your profile, units (metric/imperial), and language - RAM: ~1 GB idle (all services combined), ~1.5 GB under active use
- CPU: 2 cores recommended (Celery workers are the main consumer)
- Disk: 2 GB base, 10+ GB if syncing exercise images and videos - wger vs Fittrackee: Which Fitness Tracker?
- How to Self-Host Fittrackee
- Best Self-Hosted Health & Fitness Tools
- Docker Compose Basics
- Reverse Proxy Guide
- Backup Strategy
- Best Self-Hosted Analytics Tools
- Getting Started with Self-Hosting