Wine Cellar logo

Wine Cellar

Self-hosted wine management app for tracking your collection and tasting notes

Alternative to: vivino, cellartracker


Wine Cellar is a self-hosted wine management app built with Django for wine enthusiasts to track wines, store tasting notes, rate bottles, and manage inventory. It supports multi-user hosting, barcode scanning to add or remove bottles, food pairing notes, and email reminders for wines nearing their drink-by date.

Wine Cellar Docker Compose example

Self-host Wine Cellar on your own server, homelab, or VPS starting from this Docker Compose example. It runs Wine Cellar in Docker containers using the official ghcr.io/the-broke-sommeliers/wine-cellar-web:latest, postgres:16, redis:8-alpine, caddy:2, ghcr.io/the-broke-sommeliers/wine-cellar-web:latest, ghcr.io/the-broke-sommeliers/wine-cellar-web:latest images, with persistent volumes and automatic restarts preconfigured. Review the environment variables and adjust them to your setup, save the file as compose.yml (or docker-compose.yml), and start the stack with docker compose up -d.

services:
  web:
    image: ghcr.io/the-broke-sommeliers/wine-cellar-web:latest
    restart: unless-stopped
    command: gunicorn wine_cellar.conf.wsgi:application --bind 0.0.0.0:8000 --worker-class gthread --threads 4 --workers 2
    volumes:
      - static_volume:/home/app/web/staticfiles
      - media_volume:/home/app/web/mediafiles
    environment:
      # Marks this container as the web role (runs migrations, collectstatic, admin bootstrap on start).
      CONTAINER_ROLE: "web"
      DJANGO_DEBUG: "False"
      # Django secret key used for cryptographic signing. Set to a long random string.
      SECRET_KEY: "changeme"
      # Space-separated hostnames the app will serve (add your public domain).
      DJANGO_ALLOWED_HOSTS: "localhost 127.0.0.1 [::1]"
      # Space-separated trusted origins for CSRF (include your public https URL).
      DJANGO_CSRF_TRUSTED_ORIGINS: "http://127.0.0.1:8085"
      # Public URL of the site, used in emails and links.
      DJANGO_SITE_URL: "https://example.org"
      # Extra hosts to allow in the form-action Content-Security-Policy directive.
      DJANGO_CSP_FORM_ACTION_EXTRA: ""
      SQL_ENGINE: "django.db.backends.postgresql"
      # Name of the PostgreSQL database.
      SQL_DATABASE: "django"
      # PostgreSQL username.
      SQL_USER: "django"
      # PostgreSQL password.
      SQL_PASSWORD: "changeme"
      SQL_HOST: "db"
      SQL_PORT: "5432"
      DATABASE: "postgres"
      REDIS_URL: "redis://redis:6379"
      DJANGO_SETTINGS_MODULE: "wine_cellar.conf.docker_settings"
      # Username of the initial admin account created on first start.
      ADMIN_USER: "admin"
      # Email of the initial admin account.
      ADMIN_USER_EMAIL: "admin@example.org"
      # Password of the initial admin account.
      ADMIN_USER_PASSWORD: "changeme"
      # SMTP server hostname for outgoing email (leave empty to disable email).
      DJANGO_EMAIL_HOST: ""
      # SMTP server port.
      DJANGO_EMAIL_PORT: ""
      # SMTP username.
      DJANGO_EMAIL_USER: ""
      # SMTP password.
      DJANGO_EMAIL_PASSWORD: ""
      # Use STARTTLS for SMTP (set to True to enable; mutually exclusive with SSL).
      DJANGO_EMAIL_USE_TLS: ""
      # Use implicit SSL for SMTP (set to True to enable; mutually exclusive with TLS).
      DJANGO_EMAIL_USE_SSL: ""
      # Default From address for outgoing email.
      DJANGO_DEFAULT_FROM_EMAIL: ""
      # Sentry DSN for error reporting (optional).
      SENTRY_DSN: ""
      # Allow new users to register.
      DJANGO_ENABLE_SIGNUPS: "False"
      # Allow registration via social login providers.
      DJANGO_ENABLE_SOCIAL_SIGNUPS: "True"
      # Show the "What's new" changelog panel.
      DJANGO_ENABLE_WHATS_NEW: "True"
      # Email verification for password signups: mandatory, optional or none.
      DJANGO_ACCOUNT_EMAIL_VERIFICATION: "optional"
      # Email verification for social signups: mandatory, optional or none.
      DJANGO_SOCIALACCOUNT_EMAIL_VERIFICATION: "optional"
      DJANGO_SOCIALACCOUNT_EMAIL_AUTHENTICATION: "False"
      DJANGO_SOCIALACCOUNT_EMAIL_AUTHENTICATION_AUTO_CONNECT: "False"
      # LiteLLM model string for AI wine-label scanning (e.g. gemini/gemini-2.5-flash).
      AI_MODEL: ""
      # API key for the AI model provider.
      AI_API_KEY: ""
    depends_on:
      db:
        condition: service_started
      redis:
        condition: service_started
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health/"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 20s

  db:
    image: postgres:16
    restart: unless-stopped
    volumes:
      - postgres_data:/var/lib/postgresql/data/
    environment:
      # PostgreSQL username.
      POSTGRES_USER: "django"
      # PostgreSQL password.
      POSTGRES_PASSWORD: "changeme"
      # Name of the PostgreSQL database.
      POSTGRES_DB: "django"

  redis:
    image: redis:8-alpine
    restart: unless-stopped

  caddy:
    image: caddy:2
    restart: unless-stopped
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - static_volume:/srv/static
      - media_volume:/srv/media
      - caddy_data:/data
      - caddy_config:/config
    depends_on:
      - web

  celery:
    image: ghcr.io/the-broke-sommeliers/wine-cellar-web:latest
    restart: unless-stopped
    command: celery -A wine_cellar.conf worker -l info
    environment:
      DJANGO_DEBUG: "False"
      # Django secret key used for cryptographic signing. Set to a long random string.
      SECRET_KEY: "changeme"
      # Space-separated hostnames the app will serve (add your public domain).
      DJANGO_ALLOWED_HOSTS: "localhost 127.0.0.1 [::1]"
      SQL_ENGINE: "django.db.backends.postgresql"
      # Name of the PostgreSQL database.
      SQL_DATABASE: "django"
      # PostgreSQL username.
      SQL_USER: "django"
      # PostgreSQL password.
      SQL_PASSWORD: "changeme"
      SQL_HOST: "db"
      SQL_PORT: "5432"
      DATABASE: "postgres"
      REDIS_URL: "redis://redis:6379"
      DJANGO_SETTINGS_MODULE: "wine_cellar.conf.docker_settings"
      # SMTP server hostname for outgoing email (leave empty to disable email).
      DJANGO_EMAIL_HOST: ""
      # SMTP server port.
      DJANGO_EMAIL_PORT: ""
      # SMTP username.
      DJANGO_EMAIL_USER: ""
      # SMTP password.
      DJANGO_EMAIL_PASSWORD: ""
      # Use STARTTLS for SMTP (set to True to enable; mutually exclusive with SSL).
      DJANGO_EMAIL_USE_TLS: ""
      # Use implicit SSL for SMTP (set to True to enable; mutually exclusive with TLS).
      DJANGO_EMAIL_USE_SSL: ""
      # Default From address for outgoing email.
      DJANGO_DEFAULT_FROM_EMAIL: ""
    depends_on:
      web:
        condition: service_healthy
      redis:
        condition: service_started

  celery-beat:
    image: ghcr.io/the-broke-sommeliers/wine-cellar-web:latest
    restart: unless-stopped
    command: celery -A wine_cellar.conf beat -l info
    environment:
      DJANGO_DEBUG: "False"
      # Django secret key used for cryptographic signing. Set to a long random string.
      SECRET_KEY: "changeme"
      # Space-separated hostnames the app will serve (add your public domain).
      DJANGO_ALLOWED_HOSTS: "localhost 127.0.0.1 [::1]"
      SQL_ENGINE: "django.db.backends.postgresql"
      # Name of the PostgreSQL database.
      SQL_DATABASE: "django"
      # PostgreSQL username.
      SQL_USER: "django"
      # PostgreSQL password.
      SQL_PASSWORD: "changeme"
      SQL_HOST: "db"
      SQL_PORT: "5432"
      DATABASE: "postgres"
      REDIS_URL: "redis://redis:6379"
      DJANGO_SETTINGS_MODULE: "wine_cellar.conf.docker_settings"
    depends_on:
      web:
        condition: service_healthy
      redis:
        condition: service_started

volumes:
  postgres_data:
  static_volume:
  media_volume:
  caddy_data:
  caddy_config:

Values set to changeme are required — replace them with your own values before starting Wine Cellar.

Extra files

The Docker Compose configuration above bind-mounts the following file. Save it next to your compose.yml, keeping the same relative path.

Caddyfile

:80 {

@files {
	path /static/* /media/*
}
handle @files {
		file_server
	}

handle {
	@health path /health
    respond @health "Not Found" 404
	reverse_proxy web:8000
}
}

Prefer a managed setup? WinterFlow installs, configures, and updates Wine Cellar for you using this same Docker Compose configuration.