What Is wger?
wger (pronounced "veger") is an open-source fitness tracking application that covers workouts, nutrition logging, and body measurements. It comes with a library of 800+ exercises (synced from wger.de), meal planning with nutritional data, and a REST API for building custom frontends or integrations. Self-hosting means your workout data, body measurements, and dietary information stay private.
Prerequisites
- 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)
Docker Compose Configuration
wger requires several services: the application server, PostgreSQL, Redis, Nginx, and Celery workers for background tasks. This looks complex but the official stack works reliably out of the box.
Create a docker-compose.yml file:
services:
web:
image: wger/server:2.4
container_name: wger
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", "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
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
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
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
restart: unless-stopped
command: /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
restart: unless-stopped
command: /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
Create the Nginx configuration file nginx.conf:
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;
}
}
Start the stack:
docker compose up -d
First startup takes 1-2 minutes as Django runs migrations and Celery begins syncing exercises from wger.de.
Initial Setup
- 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
Important: After creating your admin account, consider setting ALLOW_REGISTRATION=False and ALLOW_GUEST_USERS=False if this is a personal instance.
The Celery workers automatically sync the exercise database from wger.de in the background. This includes 800+ exercises with descriptions, muscles targeted, and (if enabled) images and videos. The initial sync can take 10-30 minutes.
Configuration
| Setting | Variable | Default | Notes |
|---|---|---|---|
| Registration | ALLOW_REGISTRATION |
True |
Set False for private instances |
| Guest access | ALLOW_GUEST_USERS |
True |
Set False to require accounts |
| Exercise sync | SYNC_EXERCISES_CELERY |
True |
Weekly sync from wger.de |
| Image sync | SYNC_EXERCISE_IMAGES_CELERY |
True |
Can use several GB of disk |
| Video sync | SYNC_EXERCISE_VIDEOS_CELERY |
True |
Uses significant disk space |
| Timezone | TIME_ZONE |
Europe/Berlin |
Set to your timezone |
| Brute force protection | AXES_ENABLED |
True |
10 failed attempts = lockout |
Reverse Proxy
If placing wger behind an external reverse proxy (Nginx Proxy Manager, Caddy, Traefik), point it to port 80 on the wger Nginx container. Add these environment variables to the web service:
X_FORWARDED_PROTO_HEADER_SET: "True"
CSRF_TRUSTED_ORIGINS: "https://fitness.example.com"
For a dedicated reverse proxy setup, see Reverse Proxy Guide.
Backup
Back up the PostgreSQL database and media volume:
# Database
docker exec wger-db pg_dump -U wger wger > wger-backup.sql
# Media files (exercise images, user uploads)
docker run --rm -v wger-media:/data -v $(pwd):/backup alpine tar czf /backup/wger-media.tar.gz /data
For general backup strategies, see Backup Strategy.
Troubleshooting
Exercise Database Is Empty
Symptom: No exercises appear in the exercise list after setup.
Fix: The Celery worker syncs exercises asynchronously. Check if it's running: docker logs wger-celery-worker. The initial sync takes 10-30 minutes. If the worker shows errors connecting to wger.de, check outbound internet access.
Static Files Not Loading (Broken CSS/JS)
Symptom: The site loads but looks unstyled or broken.
Fix: Nginx serves static files from the shared wger-static volume. Ensure the nginx.conf paths match the volume mounts. Run docker exec wger python manage.py collectstatic --noinput to regenerate static files.
"CSRF Verification Failed" Behind Reverse Proxy
Symptom: Form submissions fail with a CSRF error when accessed through a reverse proxy.
Fix: Set CSRF_TRUSTED_ORIGINS to your external domain (e.g., https://fitness.example.com) and X_FORWARDED_PROTO_HEADER_SET=True in the web service environment.
Resource Requirements
- 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
The latest tag on Docker Hub points to 2.5-dev (development). Always pin to 2.4 for stable deployments.
Verdict
wger is the most comprehensive self-hosted fitness tracker available. The exercise database, workout planning, nutritional tracking, and body measurement logging cover everything a serious fitness enthusiast needs. The 6-service Docker stack is heavier than simpler alternatives, but the feature depth justifies it.
If you just want basic workout logging without the complexity, Fittrackee is lighter and focused on GPS-tracked activities. wger is the answer when you want a full gym-style workout planner with nutritional tracking.
Top comments (0)