[Blog](https://deploywithox.com/blog) [Guides](https://deploywithox.com/blog#c-guides) # How to self-host Saleor without Docker on your own VPS Run Saleor 3.23.41, the open-source GraphQL commerce API, on your own Ubuntu server with ox: Postgres, Redis and a Celery worker, from the config that ran. Saurav Sharma Oct 17, 2026 9 min read On this page - [What you need](https://deploywithox.com/blog/self-host-saleor#what-you-need) - [The steps](https://deploywithox.com/blog/self-host-saleor#steps) - [The ox.toml, line by line](https://deploywithox.com/blog/self-host-saleor#config) - [Postgres and Redis](https://deploywithox.com/blog/self-host-saleor#services) - [Updates and backups](https://deploywithox.com/blog/self-host-saleor#updates) - [Frequently asked questions](https://deploywithox.com/blog/self-host-saleor#faq) To self-host Saleor you need an Ubuntu server, a fork of the Saleor repo with one `ox.toml` file in it, and twelve variables, one of which ox makes for you. In our run the first deploy took about 9 minutes, and ox set up the Postgres and Redis that Saleor uses and ran its Celery worker and scheduler beside it. **How we tested** On Oct 17, 2026, ox deployed Saleor 3.23.41 from commit [`28a1227b`](https://github.com/saleor/saleor/commit/28a1227b9a2db153c22ed6165b0987d0c96f80f8) on a fresh DigitalOcean server with 8 vCPUs and 16 GB of memory. The run then redeployed it, backed it up and rebooted the server. Then a separate test agent, one that did not write the config, signed in, ran GraphQL queries in the playground for the shop and for the new account, and got both after a reload, and checked the app's services and its backup. [Saleor](https://github.com/saleor/saleor) is an open-source commerce platform with no shop front of its own. Its README calls it a GraphQL native, API-only platform, where the API is the only way to use, configure or extend the backend. The code is BSD 3-Clause licensed, and its maker, Saleor Commerce, also runs a hosted Saleor Cloud. This guide deploys the API, a Django app served by uvicorn. A Celery worker runs background tasks such as account emails, and a Celery beat scheduler queues the timed ones. PostgreSQL holds the data, and Redis is the broker and the cache. The Saleor Dashboard and the storefront are separate repositories that the run did not deploy. ox deployed Saleor 3.23.41 (commit 28a1227b) on Oct 17, 2026 on a fresh DigitalOcean server with 8 vCPUs and 16 GB of memory; the first deploy took 9 minutes and a redeploy took 3 minutes. - **First deploy:** 9 minutes, from adding the project until the app passed the run's first checks. - **Redeploy of the same commit:** 3 minutes, until the app passed the checks again. - **Server:** DigitalOcean, 8 vCPUs and 16 GB of memory (`s-8vcpu-16gb`), with this app alone on it. Every one of the 85 apps we deployed ran on this one size, so it was not picked for this app. - **Memory in use after the reboot:** 4296 MB of 15986 MB, for the whole server: Ubuntu, the app and its staging copy, its services, Caddy and the ox agent. On a server with no apps, ox's tests on real servers hold the agent to 40 MB or less and Caddy to 50 MB or less. - **Services ox runs for it:** `postgres`, `redis`. - **Background processes:** `beat`, `worker`, each its own systemd unit beside the web process. - **Source:** [github.com/saleor/saleor](https://github.com/saleor/saleor) at 3.23.41, commit [`28a1227b`](https://github.com/saleor/saleor/commit/28a1227b9a2db153c22ed6165b0987d0c96f80f8). The Saleor GraphQL playground at the API's address A me query answered for the signed-in test account One query that reads the shop's name and the signed-in account ## What you need - An Ubuntu LTS server where you can run one command as root. The run used the size in the facts above, with Saleor and its staging copy on it. - A GitHub account to fork `saleor/saleor`, and an ox account, which you open by signing in with GitHub. - An SMTP server, if you want Saleor's emails to leave the machine. The run pointed `EMAIL_URL` at a test mail catcher, so real delivery was not tested. A staff account, the Dashboard, a storefront, product uploads and payments were not tested. ## The steps In the ox dashboard, press Add a server and run the install command it shows, as root on your server. Fork `saleor/saleor` on GitHub and check out the tag `3.23.41`, the release the run deployed. Saleor's README warns that its main branch is the development version and may be unstable. Add the `ox.toml` shown below at the repo root, commit, and push. Then write `saleor.env` on your laptop with every key from the variables section except `RSA_PRIVATE_KEY`, which ox makes. With the [ox CLI](https://deploywithox.com/docs/cli) signed in, run: Shell ```bash ox new /saleor ox review saleor --from-file saleor.env --generate RSA_PRIVATE_KEY --wait ``` The project takes the repo's name. The review saves your values, makes the RSA key and starts the first deploy, and `--wait` prints the run's log until it ends. The [quickstart](https://deploywithox.com/docs/quickstart) shows the same steps in the browser. When the deploy is green, open the address ox shows for the project and add `/graphql/` to it. That opens the GraphQL Playground, shown in the first screenshot. With `DEBUG` off, Saleor serves no page at the root address, and the playground stays on unless `PLAYGROUND_ENABLED` says otherwise. The test agent ran `{ shop { name } }` and got the shop's name, "Saleor e-commerce". With no storefront to sign up on, it used the `accountRegister` mutation with an example.com address. The worker sent the confirmation email to the mail catcher, and `confirmAccount` confirmed the account. A wrong password was refused as `INVALID_CREDENTIALS`. The right one made `tokenCreate` return a token, and with it as an `Authorization: Bearer` header, `{ me { email } }` answered with the new account. After a reload, one query returned both the shop and the account. The run served Saleor on its preview address only. A domain of your own was not part of it, and the [domains page](https://deploywithox.com/docs/operate/domains) covers adding one. ## The ox.toml, line by line TOML ```toml # Saleor 3.23.41 (Django + GraphQL, a Celery worker and beat, postgres and # redis). Detection finds the Django app, its uvicorn start, migrate and # collectstatic; the file adds what it cannot know: the worker and the # beat scheduler (the Dockerfile and docs run them as `celery -A # saleor.celeryconf:app worker` and `beat`) and the two services. # The Dockerfile's runtime libraries that Python does not bring: python-magic # needs libmagic (collectstatic loads the plugins that import it). packages = ["libmagic1t64", "media-types"] [build] # The Dockerfile syncs with the dev group (no --no-dev): the production # code imports saleor/account/tests/fixtures through core/utils/random_data, # and that needs pytest (findings G8). install = "uv sync --frozen" [workers] worker = "exec .venv/bin/celery -A saleor.celeryconf:app worker -E" beat = "exec .venv/bin/celery -A saleor.celeryconf:app beat --scheduler saleor.schedulers.schedulers.DatabaseScheduler" [services] postgres = {} redis = {} ``` ox finds the Django project by itself. It runs `migrate` and `collectstatic` and starts uvicorn, the server Saleor's Dockerfile uses, and `ox check` in your checkout prints that plan offline. The file adds only what detection cannot know. - `packages` installs two Ubuntu libraries. Saleor imports `python-magic` in several modules, which needs `libmagic`, and Ubuntu 26.04 names that package `libmagic1t64`. `media-types`, also in Saleor's Dockerfile, lets it identify the type of an uploaded file. - `install` syncs the Python dependencies from `uv.lock`, dev group included, as the Dockerfile's sync does. Saleor's `random_data` module imports a test fixture that imports `pytest`, so production needs pytest too. - The two `[workers]` are the commands Saleor's `pyproject.toml` lists as its worker and scheduler tasks. `-E` turns on task events. Beat uses Saleor's `DatabaseScheduler`, so the schedule sits in Postgres and no schedule file needs keeping. Each runs as its own systemd unit. - `[services]` asks for Postgres and Redis with their defaults. ox hands the app `DATABASE_URL` and `REDIS_URL`, and Saleor reads `DATABASE_URL` as it is. Shell ```bash DEBUG SECRET_KEY RSA_PRIVATE_KEY ALLOWED_HOSTS CELERY_BROKER_URL CACHE_URL ALLOWED_CLIENT_HOSTS DEFAULT_FROM_EMAIL EMAIL_URL HTTP_IP_FILTER_ALLOW_LOOPBACK_IPS DASHBOARD_URL MEDIA_ROOT ``` `DEBUG` defaults to on in Saleor's `settings.py`, so the run turned it off. With it off, Saleor refuses to start without `ALLOWED_CLIENT_HOSTS` and `RSA_PRIVATE_KEY`. The key signs its JSON Web Tokens and has to be a PEM. `--generate RSA_PRIVATE_KEY` makes a 2048-bit one. `SECRET_KEY` is Django's signing key. Give it a long random value of your own. `ALLOWED_HOSTS` is `${PUBLIC_HOST}`, a reference ox fills in with the project's host name, and Django refuses any other Host header. `ALLOWED_CLIENT_HOSTS` is the same reference. Saleor accepts a `redirectUrl` only for a listed host, and `accountRegister` needs one while email confirmation is on. `CELERY_BROKER_URL` and `CACHE_URL` both point at `${REDIS_URL}`. Without a broker URL, Saleor runs its tasks inside the web process instead of handing them to the worker. `EMAIL_URL` is an `smtp://` address and `DEFAULT_FROM_EMAIL` the sender, as in Saleor's `.env.example`. The run pointed the first at a test mail catcher and used an example.com sender. `HTTP_IP_FILTER_ALLOW_LOOPBACK_IPS` controls Saleor's filter on outgoing calls to addresses people supply, such as webhooks: loopback addresses are refused unless it is true. The default is false, and the `.env.example` and the run set it true. `DASHBOARD_URL`, built from `${PUBLIC_URL}` in the run, feeds only the start page Saleor serves in debug mode, so it did nothing here. `MEDIA_ROOT` pointed at a folder under `${OX_DATA_DIR}`, ox's data directory, but Saleor 3.23.41 fixes its media folder in `settings.py` and does not read this key. See [variables](https://deploywithox.com/docs/operate/variables) for how references work. ## Postgres and Redis Postgres is shared with other apps on the server, each with its own database and user. Redis runs for this project alone, as its own unit. The run also deployed a staging copy beside production, and the memory figure above counts both. To look inside them, open the Services tab or run `ox explore saleor postgres` or `ox explore saleor redis`. ## Updates and backups To move to a new release, merge its tag into your fork and push, or run `ox deploy saleor --wait`. The migrations run on every deploy, and ox dumps Postgres first. The run's redeploy left such a dump. Saleor keeps a `CHANGELOG.md` in its repo, so read it before a version jump. Backups need no setup. The run's backups held a Postgres dump, both daily and from Back up now, and a small Redis archive made daily. `ox backups saleor` lists them. A restore was not tested. The [backups page](https://deploywithox.com/docs/operate/backups) covers copies in an S3 bucket. ## Frequently asked questions ### Do I need Docker to self-host Saleor? No. Saleor's README points to a Docker Compose guide for installing, and to its contributing guide for local development without Docker. ox builds the app from your fork and runs the API, the worker and the scheduler as systemd services, with Postgres and Redis beside them. There is no image to pull. ### How do I create the first Saleor account? The API has no sign-up page, since the storefront is a separate app. A customer account comes from the accountRegister mutation, then confirmAccount with the emailed token, and tokenCreate signs in. The run did the same and read the account with a me query. A staff account for the Dashboard was not made, so it was not tested. ### Does Saleor come with a dashboard or a storefront? No. This repo is the API only, and its README sends you to saleor-dashboard for the admin interface and to a separate repo for an example storefront. The run deployed the API and used its GraphQL playground. The Dashboard and a storefront were not part of it, so neither was tested. ### How much memory does self-hosted Saleor use? On the run's server, the whole machine used the memory shown in the facts after a reboot. That counts Saleor, its staging copy, both Celery processes, Postgres, Redis, Caddy and the ox agent. ox takes any server from 1 GB up, and says so when memory is why an app stopped. ### Why does Saleor need an RSA private key? Saleor signs its JSON Web Tokens with it. With DEBUG on, Saleor writes a key file for itself. With DEBUG off, it raises an error when RSA_PRIVATE_KEY is empty, and it loads the value only as a PEM. In ox, `ox review --generate RSA_PRIVATE_KEY` makes a valid one. New to running apps on your own server? [How ox self-hosts your apps](https://deploywithox.com/self-hosting) shows what ox sets up on your server. On this page - [What you need](https://deploywithox.com/blog/self-host-saleor#what-you-need) - [The steps](https://deploywithox.com/blog/self-host-saleor#steps) - [The ox.toml, line by line](https://deploywithox.com/blog/self-host-saleor#config) - [Postgres and Redis](https://deploywithox.com/blog/self-host-saleor#services) - [Updates and backups](https://deploywithox.com/blog/self-host-saleor#updates) - [Frequently asked questions](https://deploywithox.com/blog/self-host-saleor#faq) - [Do I need Docker to self-host Saleor?](https://deploywithox.com/blog/self-host-saleor#faq-docker) - [How do I create the first Saleor account?](https://deploywithox.com/blog/self-host-saleor#faq-account) - [Does Saleor come with a dashboard or a storefront?](https://deploywithox.com/blog/self-host-saleor#faq-dashboard) - [How much memory does self-hosted Saleor use?](https://deploywithox.com/blog/self-host-saleor#faq-memory) - [Why does Saleor need an RSA private key?](https://deploywithox.com/blog/self-host-saleor#faq-rsa) Written by Saurav Sharma Builds ox. Writes about running apps on servers you own, without Docker and without a platform bill. [Older post How to self-host SearXNG on your own VPS, no Docker](https://deploywithox.com/blog/self-host-searxng) [Newer post How to self-host Redmine without Docker on your own VPS](https://deploywithox.com/blog/self-host-redmine) ## Keep reading - Guides [Self-Hosted Open-Source Apps Deployed with ox: All 85](https://deploywithox.com/blog/self-hosted-open-source-apps-deployed-with-ox) ox deployed 85 open-source apps from pinned commits on fresh servers, with no Docker. The stacks, services, deploy times and a table of every app. Saurav Sharma Oct 17, 2026 16 min read - Guides [How to self-host AFFiNE on your own VPS without Docker](https://deploywithox.com/blog/self-host-affine) Run AFFiNE v0.27.4 on your own Ubuntu server with ox: Postgres with pgvector, Redis, a Rust and Node build, and an admin made from two variables. Saurav Sharma Oct 17, 2026 12 min read - Guides [How to self-host Akaunting on your own VPS without Docker](https://deploywithox.com/blog/self-host-akaunting) Run Akaunting 3.2.4 on your own Ubuntu server with ox: PHP, MariaDB, a scheduler and the first company and admin made on deploy, from the config that ran. Saurav Sharma Oct 17, 2026 10 min read ## Deploy to a server you own ox sets up systemd, Caddy and PostgreSQL on your Ubuntu server and deploys on every push. Free for your first project. [Sign up with GitHub](https://deploywithox.com/login/github) [Read the docs](https://deploywithox.com/docs)