Self-Hosting Saleor with Docker Compose

What Is Saleor?

Saleor is an open-source headless e-commerce platform built with Python and GraphQL. “Headless” means it provides the backend (products, orders, payments, inventory) through an API, while you build or choose your own frontend storefront. Think of it as a self-hosted alternative to Shopify’s backend, minus the built-in themes — you get an admin dashboard and a GraphQL API, then connect whatever frontend you want.

Important: Saleor Is Headless

Before you dive in, understand what “headless” means for self-hosters:

ComponentIncluded?Notes
Admin dashboardYesManage products, orders, customers
GraphQL APIYesFull commerce API
Customer storefrontNoYou need to build or deploy one separately
Payment processingPlugin-basedStripe, Braintree, Adyen via apps
Email templatesBasicCustomizable via dashboard

If you want a traditional all-in-one e-commerce platform with a built-in storefront, look at WooCommerce or PrestaShop instead. Saleor is for teams that want API-first commerce with full frontend control.

Prerequisites

  • A Linux server (Ubuntu 22.04+ recommended)
  • Docker and Docker Compose installed (guide)
  • 4 GB of free RAM minimum (Saleor recommends 5 GB for Docker)
  • 15 GB of free disk space
  • A domain name (recommended for the API and dashboard)

Docker Compose Configuration

Saleor’s production stack needs five services: the API server, a Celery worker for background tasks, PostgreSQL, Valkey (Redis-compatible cache), and the admin dashboard.

Create a docker-compose.yml:

services:
  api:
    image: ghcr.io/saleor/saleor:3.22.41
    container_name: saleor-api
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started
    environment:
      DATABASE_URL: "postgres://saleor:${DB_PASSWORD:-changeme_strong_password}@db:5432/saleor"
      CACHE_URL: "redis://cache:6379/0"
      CELERY_BROKER_URL: "redis://cache:6379/1"
      SECRET_KEY: "${SECRET_KEY:-changeme_generate_with_openssl}"  # CHANGE — openssl rand -hex 32
      ALLOWED_HOSTS: "localhost,127.0.0.1,api,${API_DOMAIN:-localhost}"
      DASHBOARD_URL: "${DASHBOARD_URL:-http://localhost:9000/}"
      DEFAULT_FROM_EMAIL: "store@example.com"
      EMAIL_URL: "smtp://mail.example.com:587/?user=store@example.com&password=smtp-password&ssl=true"
      DEFAULT_CHANNEL_SLUG: "default-channel"
    volumes:
      - saleor-media:/app/media   # Product images and uploads
    ports:
      - "8000:8000"               # GraphQL API
    networks:
      - saleor-backend
    restart: unless-stopped

  worker:
    image: ghcr.io/saleor/saleor:3.22.41
    container_name: saleor-worker
    command: celery -A saleor --app=saleor.celeryconf:app worker --loglevel=info -B
    depends_on:
      - db
      - cache
    environment:
      DATABASE_URL: "postgres://saleor:${DB_PASSWORD:-changeme_strong_password}@db:5432/saleor"
      CACHE_URL: "redis://cache:6379/0"
      CELERY_BROKER_URL: "redis://cache:6379/1"
      SECRET_KEY: "${SECRET_KEY:-changeme_generate_with_openssl}"
      DEFAULT_FROM_EMAIL: "store@example.com"
      EMAIL_URL: "smtp://mail.example.com:587/?user=store@example.com&password=smtp-password&ssl=true"
    volumes:
      - saleor-media:/app/media
    networks:
      - saleor-backend
    restart: unless-stopped

  dashboard:
    image: ghcr.io/saleor/saleor-dashboard:3.22.35
    container_name: saleor-dashboard
    ports:
      - "9000:80"                 # Admin dashboard
    restart: unless-stopped

  db:
    image: postgres:15-alpine
    container_name: saleor-db
    environment:
      POSTGRES_USER: saleor
      POSTGRES_PASSWORD: "${DB_PASSWORD:-changeme_strong_password}"  # CHANGE
      POSTGRES_DB: saleor
    volumes:
      - saleor-db:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U saleor"]
      interval: 10s
      timeout: 5s
      retries: 10
    networks:
      - saleor-backend
    restart: unless-stopped

  cache:
    image: valkey/valkey:8.1-alpine
    container_name: saleor-cache
    volumes:
      - saleor-cache:/data
    networks:
      - saleor-backend
    restart: unless-stopped

volumes:
  saleor-db:
  saleor-cache:
  saleor-media:

networks:
  saleor-backend:
    driver: bridge

Create a .env file:

# Generate before first start:
# openssl rand -hex 32
SECRET_KEY=your_generated_secret_here

# Database password
# openssl rand -hex 16
DB_PASSWORD=your_database_password_here

# Your domains (for production)
API_DOMAIN=api.store.example.com
DASHBOARD_URL=https://admin.store.example.com/

Start the stack:

docker compose up -d

First startup takes 1-2 minutes for database migrations. Monitor:

docker logs -f saleor-api

Initial Setup

  1. Open the dashboard at http://your-server-ip:9000
  2. The dashboard will ask for the API URL — enter http://your-server-ip:8000/graphql/
  3. Create your admin account through the API first:
docker exec -it saleor-api python manage.py createsuperuser
  1. Log in to the dashboard with your admin credentials
  2. Set up your first sales channel, warehouse, and shipping zone

First Steps in the Dashboard

StepWhereWhat to Configure
1Configuration → ChannelsCreate a sales channel (currency, country)
2Configuration → WarehousesAdd a warehouse with shipping zones
3Configuration → ShippingDefine shipping methods and rates
4Catalog → ProductsAdd your first product with variants
5Configuration → TaxesSet up tax configuration

Building a Storefront

Saleor provides starter storefronts you can deploy alongside the API:

React Storefront (official):

npx degit saleor/storefront my-store
cd my-store
npm install
# Set SALEOR_API_URL=http://your-server-ip:8000/graphql/
npm run dev

The storefront connects to your Saleor API via GraphQL. Any frontend framework works — Next.js, Nuxt, Remix, or even a mobile app.

Reverse Proxy

For production, you need HTTPS on both the API and dashboard. Caddy example:

api.store.example.com {
    reverse_proxy localhost:8000
}

admin.store.example.com {
    reverse_proxy localhost:9000
}

store.example.com {
    reverse_proxy localhost:3000  # Your storefront
}

Update ALLOWED_HOSTS and DASHBOARD_URL in your environment to match:

ALLOWED_HOSTS=localhost,api,api.store.example.com
DASHBOARD_URL=https://admin.store.example.com/

See our Reverse Proxy Guide for Nginx Proxy Manager and Traefik configurations.

Backup

Back up the database and media files:

# Database
docker exec saleor-db pg_dump -U saleor saleor > saleor-backup-$(date +%Y%m%d).sql

# Media (product images, uploads)
docker run --rm -v saleor-media:/data -v $(pwd):/backup \
  alpine tar czf /backup/saleor-media-$(date +%Y%m%d).tar.gz /data

See our Backup Strategy Guide for automated approaches.

Troubleshooting

Dashboard Can’t Connect to API

Symptom: Dashboard shows “Could not connect to the API” after setup.

Fix: The dashboard is a static React app served by Nginx. It connects to the API from the user’s browser, not from the server. The API URL must be reachable from the user’s machine, not just from the Docker network:

  • If accessing locally: http://localhost:8000/graphql/
  • If accessing remotely: https://api.store.example.com/graphql/
  • Add the correct host to ALLOWED_HOSTS

Worker Not Processing Orders

Symptom: Orders stuck in processing, emails not sent, webhooks not firing.

Fix: Check the Celery worker:

docker logs saleor-worker

Common issues: Redis/Valkey not running, or CELERY_BROKER_URL doesn’t match the cache service. The worker uses Redis database index 1 (/1), while the API cache uses index 0 (/0).

Media Files 404

Symptom: Product images return 404 errors.

Fix: The saleor-media volume must be shared between the api and worker services (already configured in the compose file above). If using object storage (S3/MinIO), configure:

environment:
  AWS_MEDIA_BUCKET_NAME: "saleor-media"
  AWS_STORAGE_BUCKET_NAME: "saleor-static"
  AWS_S3_ENDPOINT_URL: "http://minio:9000"

Database Migration Failures

Symptom: API container restarts with migration errors.

Fix: Run migrations manually:

docker exec -it saleor-api python manage.py migrate

If upgrading between Saleor versions, always read the release notes — some upgrades require data migration steps.

Resource Requirements

MetricValue
RAM (idle)~800 MB (API + worker + PostgreSQL + Valkey + dashboard)
RAM (active, with traffic)2-4 GB
CPUMedium-High (Python uvicorn + Celery workers)
Disk~1.5 GB for application + media storage growth

Verdict

Saleor is the most feature-complete self-hosted headless e-commerce platform available. The GraphQL API is well-designed, the admin dashboard is polished, and the multi-channel/multi-warehouse support is genuinely enterprise-grade. If you’re building a custom storefront and want full control over the commerce backend, Saleor is the right choice.

The trade-off is complexity. Five Docker services, a separate storefront deployment, and Python/Celery to maintain — this is not a weekend project. If you want something simpler with a built-in storefront, self-host WordPress with WooCommerce instead. If you want headless but prefer Node.js, look at Medusa.

Frequently Asked Questions

Do I need to build a storefront separately?

Yes. Saleor is headless — it provides the backend API and admin dashboard, but no customer-facing store. You need to build or deploy a storefront using React, Next.js, or any framework that can consume a GraphQL API. Saleor provides a React starter template to get started faster.

How does Saleor compare to WooCommerce?

WooCommerce is an all-in-one solution with themes and plugins that works out of the box. Saleor gives you an API and admin dashboard — you build the frontend yourself. Choose WooCommerce for a traditional online store. Choose Saleor if you need API-first commerce with full frontend control across web, mobile, and other channels.

Can Saleor handle multiple currencies and languages?

Yes. Saleor has built-in multi-channel support with per-channel pricing, currencies, and localization. You can run different storefronts for different markets from a single backend instance.

What payment processors does Saleor support?

Saleor supports Stripe, Braintree, and Adyen through its app/plugin system. Payment apps are configured through the admin dashboard. You can also build custom payment integrations using the Saleor App framework.

Is Saleor suitable for small stores?

It works, but it’s overkill. Five Docker containers, 2-4 GB RAM, and a separate storefront deployment make Saleor better suited for medium-to-large stores or developer teams. For small stores, WordPress with WooCommerce or PrestaShop are simpler to deploy and maintain.

Can I migrate from Shopify to Saleor?

There’s no built-in Shopify import tool. You’d need to export your Shopify data (products, customers, orders) via CSV or Shopify’s API and import it through Saleor’s GraphQL API. Product data transfers well; order history and customer accounts require custom migration scripts.

Comments