Self-hosting
Nutria runs as two containers, the app and PostgreSQL, from the compose.yaml in the repository.
Prerequisites
Section titled “Prerequisites”- Docker with Docker Compose.
Nutria runs wherever Docker runs Linux containers: Linux, and macOS or Windows with Docker Desktop.
1. Get the code
Section titled “1. Get the code”Clone the repository and check out the latest release. Replace vX.Y.Z with the newest version on the releases page.
git clone https://github.com/alvfdz/nutria.gitcd nutriagit checkout vX.Y.Zcp .env.example .env2. Configure .env
Section titled “2. Configure .env”Three values are required. Compose refuses to start while the password or the secret is empty.
| Variable | What it is | Requirements |
|---|---|---|
POSTGRES_PASSWORD |
Database password | Any strong password without @ / : # |
BETTER_AUTH_SECRET |
Signs the session cookies | A random string of at least 32 characters |
BETTER_AUTH_URL |
The address you open Nutria at | The full origin, e.g. http://localhost:3000 or https://nutria.example.com |
Generate the password and the secret however you like. For example, with openssl:
openssl rand -hex 24 # POSTGRES_PASSWORDopenssl rand -base64 32 # BETTER_AUTH_SECRETOptional settings:
| Variable | Default | What it does |
|---|---|---|
APP_PORT |
3000 |
Port on the host |
TZ |
UTC |
Your time zone (e.g. Europe/Madrid); decides when “today” starts |
POSTGRES_USER, POSTGRES_DB |
— | Database user and name |
TRUSTED_PROXIES |
— | Only behind a reverse proxy; see Reverse proxy |
3. Start it
Section titled “3. Start it”docker compose up -d --build --waitOpen BETTER_AUTH_URL (by default http://localhost:3000). A fresh install takes you to the setup screen to create your account; see First run.
What runs where
Section titled “What runs where”- The app container applies any pending database migrations every time it starts, then serves Nutria.
- The database publishes no port: only the app can reach it, inside the compose network. Its data lives in a named volume and survives restarts and rebuilds.
- Every response carries standard security headers and a strict Content Security Policy.
Troubleshooting
Section titled “Troubleshooting”“The server refused this request” on the setup or login screen means Better Auth didn’t trust where the request came from. Check the app’s logs with docker compose logs app:
ERROR [Better Auth]: Invalid origin: https://nutria.example.com(orInvalid callbackURL: …): the address in your browser doesn’t matchBETTER_AUTH_URL. Set it to exactly the address the line names (scheme, host and port) and restart withdocker compose up -d --wait.ERROR [Better Auth]: Blocked cross-site navigation login attempt: a page on another website tried to sign in to your Nutria, and Better Auth’s cross-site request forgery protection blocked it. Nutria’s own screens never trigger this, so there’s nothing to change.
“The server refused this request because the browser didn’t say where it came from” means the request reached Nutria without an Origin header, which leaves nothing in the logs. If Nutria is behind a reverse proxy, make sure it forwards the Origin header unchanged.
“The server ran into a problem” means the request reached Nutria but failed there, e.g. the database refused the new account. Check the app’s logs with docker compose logs app.