# Run it on a server

> Host Oatmilk for your team on a VPS or your own server at your domain, with HTTPS from Let's Encrypt.

Source: https://app.getoatmilk.com/docs/self-hosting/server

This guide puts Oatmilk on one Linux server at your own domain, such as `books.example.com`, for you and your team. It works the same on Hetzner, DigitalOcean, Linode, OVH, a homelab machine or any other host with Docker. Everything runs on the server; for managed databases, see the [AWS](https://app.getoatmilk.com/docs/self-hosting/aws.md), [Google Cloud](https://app.getoatmilk.com/docs/self-hosting/google-cloud.md) and [Azure](https://app.getoatmilk.com/docs/self-hosting/azure.md) guides.

## 1. Get a server

- Ubuntu 24.04 or another current Linux.
- 2 CPUs, 8 GB of memory and 40 GB of disk. The build needs the memory; Oatmilk itself runs in about 4 GB.
- A public IP address, with ports 80 and 443 open to the internet.

Log in with SSH as a user that can run `sudo`.

## 2. Point your domain at it

At your DNS provider, add an `A` record (and an `AAAA` record for IPv6) for the name people will open, pointing at the server's IP address:

```
books.example.com.   A   203.0.113.10
```

Check it from your own computer before you continue. Caddy can only get a certificate once the name leads to the server.

```bash
dig +short books.example.com
```

## 3. Install Docker, Bun and Git

```bash
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker "$USER"         # then log out and back in
curl -fsSL https://bun.sh/install | bash
sudo apt-get install -y git
docker compose version                  # 2.20 or newer
```

If the server has a firewall, open the web ports:

```bash
sudo ufw allow 22/tcp && sudo ufw allow 80/tcp && sudo ufw allow 443/tcp && sudo ufw enable
```

## 4. Get Oatmilk and run setup

```bash
git clone https://github.com/AGI-Ventures-Canada/oatmilk.git
cd oatmilk
bun install
bun run self-host setup
```

| Question | Answer |
| --- | --- |
| Where will Oatmilk run? | **On a server** |
| Domain people will open | `books.example.com` |
| Use the standard web ports, 443 and 80? | **Yes** |
| Which PostgreSQL database? | **Run one here**, or your own (see [Bring your own services](https://app.getoatmilk.com/docs/self-hosting/services.md)) |
| Which Redis? | **Run one here** |
| Where should files be kept? | **On this machine**, or an S3-compatible bucket |
| How should people sign in? | **Accounts kept here** |
| Who can make an account? | **Only people I add or invite** |
| Which AI models…? | **Vercel AI Gateway** with an API key, or a model server your team runs (see [Use local AI models](https://app.getoatmilk.com/docs/self-hosting/local-models.md)) |
| Should Oatmilk send email? | **Yes, with Resend**, with your Resend API key and the domain you send from |
| Create your own account now? | **Yes** |
| Build and start Oatmilk now? | **Yes** |

The first build takes about 10 minutes. Caddy gets a Let's Encrypt certificate the first time someone opens the site.

> [!TIP]
> To script it instead, `bun run self-host init --server --domain books.example.com` writes the settings without questions, and `bun run self-host up` starts it. `bun run self-host help` lists every option.

## 5. Sign in and invite your team

1. Open `https://books.example.com` and sign in with the account setup made.
2. Create your company and fill in its profile.
3. Invite people from **Settings › Team**. They get an email with a link, and make their account from it.

Sign-up is closed, so only people you invite, or add yourself, can make an account:

```bash
bun run self-host user add --email ada@example.com --first-name Ada --last-name Lovelace
```

Turn on two-step sign-in for administrators in their profile. [Sign-in and accounts](https://app.getoatmilk.com/docs/self-hosting/accounts.md) covers the rest.

## 6. Set up email (recommended)

Without email, people only get invitations and reminders when you copy the link and send it yourself. With [Resend](https://resend.com):

1. Add your sending domain in Resend, such as `books.example.com`, and add the DNS records it shows.
2. Wait until Resend says the domain is verified.
3. Run setup again and answer **Yes, with Resend**, or add these to `self-host/.env` and run `bun run self-host up`:

```bash title="self-host/.env"
ACCOUNTING_EMAIL_ENABLED=true
RESEND_API_KEY=re_…
OATMILK_EMAIL_DOMAIN=books.example.com
```

## 7. Connect your tools

- **API keys** from **Developers › API keys** start with `oat_live_`, and the API reference at `https://books.example.com/docs/api` uses your address.
- **AI apps:** add `https://books.example.com/api/mcp` as a remote MCP server.
- **The CLI:** `npx @getoatmilk/cli login --host https://books.example.com`.

## Keep it running

| Task | Command |
| --- | --- |
| Check its health | `bun run self-host doctor` |
| Update to a new version | `git pull`, then `bun run self-host up` |
| Back up the database and files | `bun run self-host backup` |

Schedule backups with cron, and copy them off the server:

```bash
crontab -e
# 30 3 * * * cd /home/me/oatmilk && /home/me/.bun/bin/bun run self-host backup
```

See [Update, back up and troubleshoot](https://app.getoatmilk.com/docs/self-hosting/operate.md).

## If something goes wrong

| Problem | Fix |
| --- | --- |
| The site has no certificate | The domain doesn't point at the server yet, or ports 80 and 443 are closed. Check with `dig` and `bun run self-host logs caddy`. |
| A teammate can't make an account | Sign-up is closed: invite them in **Settings › Team**, or `bun run self-host user add`. |
| Invitation emails don't arrive | Check that Resend shows the domain as verified, and read `bun run self-host logs app`. You can always copy the invitation link from **Settings › Team**. |
| The build stops with "killed" | The server ran out of memory. Use 8 GB, or add swap while building. |
