Deploy Saleor to Production
Deploy the Saleor headless e-commerce platform, built on Django and GraphQL, to production: run the API container with PostgreSQL and Redis, configure Celery workers, media storage, secrets, allowed hosts and CORS for the dashboard and storefront, and run migrations on upgrade.
Problem statement
Saleor is a headless commerce platform: a Django and GraphQL API (saleor), a React dashboard for staff, and your own storefront. The quick-start Docker setup is fine for trying it, but production needs persistent PostgreSQL, Redis, Celery workers for emails, webhooks, and imports, durable media storage, correct allowed hosts and client origins for the dashboard and storefront, and a migration step for every upgrade.
- Run the official
ghcr.io/saleor/saleorimage (pinned to a version) with Gunicorn or Uvicorn as provided. - Provide PostgreSQL and Redis; set
DATABASE_URL,CELERY_BROKER_URL,CACHE_URL, andSECRET_KEY. - Run a Celery worker from the same image for background jobs.
- Configure
ALLOWED_HOSTS,ALLOWED_CLIENT_HOSTS, and the public URL settings for the API, dashboard, and storefront domains. - Store media on S3-compatible storage, and run
python manage.py migratebefore starting a new version.
Step-by-step solution
1. Choose versions and images
Saleor releases the API, dashboard, and storefront separately. Pin each to a release and upgrade them together following the release notes:
services:
api:
image: ghcr.io/saleor/saleor:3.21
env_file: saleor.env
depends_on: [db, redis]
restart: unless-stopped
worker:
image: ghcr.io/saleor/saleor:3.21
command: celery -A saleor.celeryconf:app worker -E --loglevel=info
env_file: saleor.env
restart: unless-stopped
dashboard:
image: ghcr.io/saleor/saleor-dashboard:3.21
restart: unless-stopped
db:
image: postgres:16
volumes: [pgdata:/var/lib/postgresql/data]
redis:
image: redis:7
volumes: { pgdata: {} }
Use the version numbers of the current release you choose; the example tags only illustrate pinning.
2. Configure the API
SECRET_KEY=<long random value>
DATABASE_URL=postgres://saleor:...@db:5432/saleor
CELERY_BROKER_URL=redis://redis:6379/1
CACHE_URL=redis://redis:6379/0
ALLOWED_HOSTS=api.shop.example.com
ALLOWED_CLIENT_HOSTS=dashboard.shop.example.com,shop.example.com
PUBLIC_URL=https://api.shop.example.com/
DEFAULT_FROM_EMAIL=shop@example.com
EMAIL_URL=smtp://user:pass@smtp.example.com:587/?tls=True
Variable names come from Saleor's settings module and occasionally change between versions; check the configuration reference for the version you deploy. ALLOWED_CLIENT_HOSTS controls which origins may receive links in emails such as password resets, and PUBLIC_URL is used to build absolute URLs.
3. Media and static files
Product images go through Django storage. Configure S3-compatible storage through Saleor's settings variables (bucket, region, credentials, and custom domain), and serve images through a CDN. Saleor generates thumbnails on demand and stores them, so the storage must be writable by the API. Static files for the GraphQL playground and admin pages are collected into the image.
4. Migrations and first setup
Run migrations before each new version starts:
docker compose run --rm api python manage.py migrate
docker compose run --rm api python manage.py createsuperuser
For a demo catalog, populatedb exists, but never run it against production. Plan upgrades: some Saleor releases include long data migrations; test them on a copy of production first.
5. Webhooks, apps, and workers
Saleor's apps and webhooks deliver events asynchronously through Celery. Without a running worker, emails do not send, webhooks are delayed, and some exports never finish. Scale workers for your order volume, and monitor queue length. Put the API behind a reverse proxy with HTTPS, and give the dashboard its API URL at build or runtime according to its documentation.
Explanation
Saleor is a large Django project packaged as a container. Everything that applies to Django in production applies here: environment-based settings, PostgreSQL, a shared cache, Celery for background work, and object storage for uploads. What is specific is the split architecture: the API is the only component with database access, while the dashboard and storefront are separate frontends on their own origins, which is why host and client-origin settings matter.
Edge cases / notes
- Saleor Cloud. If you do not want to operate the stack, Saleor offers a hosted version; self-hosting gives control at the cost of operations work.
- GraphQL limits. Expensive queries can load the API heavily; use query cost limits and caching in the storefront.
- Payments. Payment gateways run as Saleor apps; their webhooks need the API to be publicly reachable over HTTPS.
Next steps
- General GraphQL deployment practices are in Deploy a Django GraphQL API in Production.
- Prepare launch with the Django E-commerce Launch Checklist.
- Configure workers with Configure Redis and Celery for Django in Production.
FAQ
Is Saleor a Django app?
Yes. The Saleor API is a Django project exposing a GraphQL API.
Do I need Celery for Saleor?
Yes, for emails, webhooks, exports, and other background work.
Where should product images be stored?
In S3-compatible object storage, served through a CDN.
How do I upgrade Saleor?
Follow the release notes, test migrations on a production copy, back up, migrate, and upgrade the API, worker, and dashboard together.