[Blog](https://deploywithox.com/blog) [Guides](https://deploywithox.com/blog#c-guides) # How to self-host Sonarr without Docker on your own VPS Run Sonarr v4.0.20.3014 on your own Ubuntu server with ox: a .NET 6 build, the webpack UI, SQLite and two variables, from the config that ran. Saurav Sharma Oct 17, 2026 11 min read On this page - [What you need](https://deploywithox.com/blog/self-host-sonarr#what-you-need) - [The steps](https://deploywithox.com/blog/self-host-sonarr#steps) - [The ox.toml, line by line](https://deploywithox.com/blog/self-host-sonarr#config) - [What it runs on](https://deploywithox.com/blog/self-host-sonarr#services) - [Updates and backups](https://deploywithox.com/blog/self-host-sonarr#updates) - [Frequently asked questions](https://deploywithox.com/blog/self-host-sonarr#faq) To self-host Sonarr you need an Ubuntu server, a fork of the Sonarr repo with an `ox.toml` and one small Python script added, and two variables for the first admin. In our run the first deploy took about 4 minutes, which covers building the .NET server and its web interface on the server itself, and ox set up no database server, because Sonarr keeps its data in SQLite files. **How we tested** On Oct 17, 2026, ox deployed Sonarr v4.0.20.3014 from commit [`cab419ad`](https://github.com/Sonarr/Sonarr/commit/cab419ade8ac7fcab5bf80394ee492abd35d5f5a) 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, added a root folder and a series, and saw the series with its episodes after a reload, and checked the app's services and its backup. [Sonarr](https://github.com/Sonarr/Sonarr) is a PVR for Usenet and BitTorrent users, in its README's words. You give it the TV series you follow. It watches RSS feeds for new episodes and can grab, sort and rename them, and its README lists SABnzbd and NZBGet among its integrations. The code is under the GNU GPL v3. The server is a .NET 6 program, its web interface is built with webpack, and its data sits in two SQLite files. ox deployed Sonarr v4.0.20.3014 (commit cab419ad) on Oct 17, 2026 on a fresh DigitalOcean server with 8 vCPUs and 16 GB of memory; the first deploy took 4 minutes and a redeploy took 3 minutes. - **First deploy:** 4 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:** 685 MB of 15986 MB, for the whole server: Ubuntu, the app, 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. - **Source:** [github.com/Sonarr/Sonarr](https://github.com/Sonarr/Sonarr) at v4.0.20.3014, commit [`cab419ad`](https://github.com/Sonarr/Sonarr/commit/cab419ade8ac7fcab5bf80394ee492abd35d5f5a). The Sonarr sign-in page The empty Sonarr series list after signing in The Sonarr series list with the added series and its 12 episodes ## What you need - An Ubuntu LTS server with an x86_64 processor, because the config publishes Sonarr for `linux-x64`. The facts above give the size the run used and the memory it measured after a reboot. - A GitHub account to fork `Sonarr/Sonarr`, and an ox account, which you open by signing in with GitHub. - A user name and a password of your choice for the first admin. - Internet access from the server. Sonarr's series search goes to its own lookup service, `skyhook.sonarr.tv`, and the run's server reached it. - No mail server and no API key. The run set up no indexer and no download client, and it did not try notifications or Library Import. ## The steps The ox dashboard has an Add a server button. It shows one install command with a single-use token. Run it as root on the Ubuntu server, which gets only the ox agent and Caddy. Fork `Sonarr/Sonarr` on GitHub and set your fork's default branch to commit `cab419ad`, the one in the box above. Add the `ox.toml` at the repo root and `distribution/ox/prepare.py`, both shown below, then commit and push. Write a file named `sonarr.env` with the two keys from the variables section, one `KEY=value` per line. With the [ox CLI](https://deploywithox.com/docs/cli) signed in, run: Shell ```bash ox new /Sonarr ox review sonarr --from-file sonarr.env --wait ``` The project is named after the repo in lower case, `sonarr`. The review saves your two values and starts the first deploy, and `--wait` prints the log until it ends. The [quickstart](https://deploywithox.com/docs/quickstart) shows the same steps in the browser. When the deploy is green, `ox status sonarr` prints the project's address. Open `/login` on it. The test agent first typed a wrong password and got "Incorrect Username or Password", then signed in with your two values and saw an empty Series list. To use it, the agent opened Settings, then Media Management, and added a root folder, `/srv/ox/sonarr/data/tv` on the run's server, which the deploy makes empty. Under Series it chose Add New, typed "The Office" into the search box, picked the show, chose the root folder and pressed Add. After a fresh page load the show was still listed, with its 12 episodes, all missing because no indexer or download client was set up. The run served Sonarr on its preview address only, so a domain of your own was not part of it. The [domains page](https://deploywithox.com/docs/operate/domains) covers how ox adds one. ## The ox.toml, line by line TOML ```toml # Sonarr v4 (a .NET 6 server with a webpack UI and a sqlite data folder), # built from source the way its build.sh does for Linux: the install is # yarn's, which its build uses (ox detects pnpm from the stale # pnpm-lock.yaml beside yarn.lock, and Volta's yarn pin is not the repo's # choice); the build runs webpack, publishes the server for linux-x64 and # puts the UI beside it, where Sonarr looks. The SDK is global.json's pin. # Sonarr reads Sonarr__Server__* from the environment over its config.xml. # The first admin. Sonarr has no sign-in until someone sets one, and the # first visitor would get to set it: before Sonarr's first start, # distribution/ox/prepare.py names this admin in config.xml, which Sonarr # adds as a user and then removes from the file. required_variables = ["ADMIN_USERNAME", "ADMIN_PASSWORD"] [tools] dotnet = "6.0.405" yarn = "1" [build] install = "yarn install --frozen-lockfile --production=false" commands = [ "yarn run build --env production", "dotnet msbuild -restore src/Sonarr.sln -p:Configuration=Release -p:Platform=Posix -p:RuntimeIdentifiers=linux-x64 -t:PublishAllRids", "cp -r _output/UI _output/net6.0/linux-x64/publish/UI", ] [app] start = "python3 -I distribution/ox/prepare.py && cd _output/net6.0/linux-x64/publish && Sonarr__Server__Port=$PORT Sonarr__Server__BindAddress=127.0.0.1 exec dotnet Sonarr.dll -nobrowser -data=$OX_DATA_DIR" health = "/ping" ``` The config repeats the steps of Sonarr's own `build.sh` for Linux and puts the pieces where Sonarr looks for them. - `required_variables` names the two admin keys. A fresh Sonarr shows its first visitor a dialog to set the login, so whoever found the address first would pick the admin. - `[tools]` installs .NET SDK 6.0.405, the version in the repo's `global.json`, and Yarn 1, which the web build uses. - `install` runs `yarn install --frozen-lockfile`, as `build.sh` does. The repo has a `pnpm-lock.yaml` beside `yarn.lock`, so the config names Yarn outright. `--production=false` keeps the development packages, because webpack is one of them. - The first build command runs webpack in production mode and writes the interface to `_output/UI`. The second publishes the server for `linux-x64`. The third copies the interface into the publish folder as `UI`, beside `Sonarr.dll`, where Sonarr serves it from. - `start` runs the script, changes to the publish folder and runs `dotnet Sonarr.dll`. Sonarr reads `Sonarr__Server__*` environment variables over its `config.xml`. `Sonarr__Server__Port` takes the port ox gives the app, and `Sonarr__Server__BindAddress=127.0.0.1` lets only Caddy reach it. `-nobrowser` stops it from trying to open a browser on the server, and `-data=$OX_DATA_DIR` puts `config.xml`, `sonarr.db` and `logs.db` in the project's data folder, which every release shares. `exec` makes the .NET process the service's main process. - `health = "/ping"` is Sonarr's own check. It needs no sign-in and answers OK once Sonarr can read its database. ox switches traffic to a new release only after it answers. There is no `[services]` table, no `[storage]` table, no worker and no cron job. Sonarr runs its own tasks inside the one app process. `distribution/ox/prepare.py` Code ``` """Runs before Sonarr at each start on ox ([app] start in ox.toml). The first admin: Sonarr keeps its users in sonarr.db, and at every start, when that database has no user yet, it adds the user config.xml names in and (hashing the password) and then deletes both elements from the file (UserService.Handle and the config file's DeleteOldValues, both on ApplicationStartedEvent). So while sonarr.db holds no user, this writes ADMIN_USERNAME and ADMIN_PASSWORD there with Forms sign-in, and Sonarr makes the admin as it starts. Once a user exists it changes nothing: a password or sign-in method changed in Settings stays. The TV folder: a series needs a root folder that exists and Sonarr can write, so $OX_DATA_DIR/tv is made for one. It is not backed up: a TV library is far too big for the daily backup. The values are read from the environment, never from a command line, and the password is never printed. """ import os import pathlib import sqlite3 import sys import xml.etree.ElementTree as ET data = pathlib.Path(os.environ["OX_DATA_DIR"]) db = data / "sonarr.db" config = data / "config.xml" def users(): """The number of users in sonarr.db: 0 before Sonarr's first start.""" if not db.exists(): return 0 con = sqlite3.connect(db.as_uri() + "?mode=ro", uri=True) try: return con.execute('SELECT COUNT(*) FROM "Users"').fetchone()[0] except sqlite3.OperationalError as e: # A first start that stopped before its migrations made the table. if "no such table" in str(e): return 0 raise finally: con.close() def name_admin(username, password): """Write the admin and Forms sign-in into config.xml, keeping the rest.""" if config.exists(): tree = ET.parse(config) root = tree.getroot() else: root = ET.Element("Config") tree = ET.ElementTree(root) for key, value in ( ("AuthenticationMethod", "Forms"), ("Username", username), ("Password", password), ): el = root.find(key) if el is None: el = ET.SubElement(root, key) el.text = value tmp = config.with_name(config.name + ".ox-new") fd = os.open(tmp, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600) with os.fdopen(fd, "wb") as f: tree.write(f, encoding="utf-8", xml_declaration=True) os.replace(tmp, config) def main(): (data / "tv").mkdir(exist_ok=True) n = users() if n: print(f"Sonarr has {n} user(s): the admin exists; ADMIN_USERNAME and ADMIN_PASSWORD are not read") return username = os.environ.get("ADMIN_USERNAME", "").strip() password = os.environ.get("ADMIN_PASSWORD", "") if not username or not password: project = os.environ.get("OX_PROJECT", "") sys.exit(f"Sonarr has no user yet and ADMIN_USERNAME or ADMIN_PASSWORD is empty: set both (ox vars set {project} --generate ADMIN_PASSWORD) and deploy again") name_admin(username, password) print(f"Sonarr has no user yet: config.xml names {username.lower()} (ADMIN_USERNAME), whom Sonarr adds as it starts; sign in at /login") main() ``` This script runs before Sonarr on every start and does two jobs. - It makes the empty `tv` folder in the data folder, so there is a place to add as a root folder. The run's backups left it out, since a TV library is too big for a daily backup. - It makes the first admin. It opens `sonarr.db` read-only and counts the users. With none, it writes the Forms sign-in method, the user name and the password into `config.xml`, a file only the app's user can read. As Sonarr starts, it adds that user with a hashed password and removes the two elements from the file. Once a user exists, the script changes nothing, so a password you change in Settings stays. The script never prints the password. If either value is empty when a user is needed, the deploy stops and names both keys. Shell ```bash ADMIN_USERNAME ADMIN_PASSWORD ``` `ADMIN_USERNAME` and `ADMIN_PASSWORD` are the first admin's login, and you choose both. Sonarr stores the user name in lower case. The script reads them only while Sonarr has no user, so changing them later does not change the account. See [variables](https://deploywithox.com/docs/operate/variables) for how ox stores values. ## What it runs on Sonarr needed no service from ox. After the reboot the run found one systemd unit, the app, active with no restarts. `sonarr.db` is the main database and has the users in it, and `logs.db` holds Sonarr's log. ## Updates and backups To move to a newer Sonarr, put its commit in your fork and push, or run `ox deploy sonarr --wait`. The run redeployed the same commit in the time shown above. A newer Sonarr was not tested. Backups need no setup. The run's backups held a compressed copy of `sonarr.db` and one of `logs.db`, both daily and from Back up now. Run `ox backups sonarr` to list them, or use the Backups tab. 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 Sonarr? No. Sonarr's README lists Windows, Linux, macOS and Raspberry Pi as supported platforms and does not mention Docker. ox builds the .NET server and its web interface from your fork on the server and runs the result as a system service. Nothing is pulled as an image, and an update is a push or one deploy command. ### Which database does Sonarr use? By default Sonarr keeps two SQLite files in its data folder, `sonarr.db` and `logs.db`, and ox backs up both. The source also reads `Sonarr__Postgres__*` settings for a Postgres database. The run used SQLite only, so Postgres was not tested here. ### How do I create the first Sonarr login? Set ADMIN_USERNAME and ADMIN_PASSWORD before the first deploy. The start script gives Sonarr that user before the site is reachable, so no stranger can claim the account first. Sign in at /login. Sonarr has a single account and no sign-up page, and later changes to the two variables do not touch it. ### How much memory does self-hosted Sonarr use? The facts above show what the run measured: the whole server after a reboot, with Sonarr, Caddy and the ox agent on it. The build also runs on the server. ox works on any server from 1 GB up and tells you when memory is why an app stopped. ### Does Sonarr download episodes as soon as it is installed? No. Sonarr looks for releases through indexers and hands them to a download client. The run added neither, and Sonarr's System page warned about both, which is normal on a new install. The series was listed with its 12 episodes marked missing. Setting up an indexer and a download client was not tested. 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-sonarr#what-you-need) - [The steps](https://deploywithox.com/blog/self-host-sonarr#steps) - [The ox.toml, line by line](https://deploywithox.com/blog/self-host-sonarr#config) - [What it runs on](https://deploywithox.com/blog/self-host-sonarr#services) - [Updates and backups](https://deploywithox.com/blog/self-host-sonarr#updates) - [Frequently asked questions](https://deploywithox.com/blog/self-host-sonarr#faq) - [Do I need Docker to self-host Sonarr?](https://deploywithox.com/blog/self-host-sonarr#faq-docker) - [Which database does Sonarr use?](https://deploywithox.com/blog/self-host-sonarr#faq-database) - [How do I create the first Sonarr login?](https://deploywithox.com/blog/self-host-sonarr#faq-login) - [How much memory does self-hosted Sonarr use?](https://deploywithox.com/blog/self-host-sonarr#faq-memory) - [Does Sonarr download episodes as soon as it is installed?](https://deploywithox.com/blog/self-host-sonarr#faq-download) 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 Stirling-PDF without Docker on your VPS](https://deploywithox.com/blog/self-host-stirling-pdf) [Newer post How to self-host SonarQube Community Build on your own VPS](https://deploywithox.com/blog/self-host-sonarqube) ## 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)