Self-hosting

Deploy on Azure

Run Oatmilk on an Azure VM with Azure Database for PostgreSQL and Azure Cache for Redis, step by step.

60 minutes · Advanced

This guide runs Oatmilk on one Azure virtual machine, with its database in Azure Database for PostgreSQL and, optionally, Redis in Azure Cache for Redis. Azure Blob Storage has no S3-compatible API, so files stay on the VM's disk or go to another S3-compatible store. Do every step in one resource group and region, for example canadacentral.

1. Create the network and the VM

In Virtual machines › Create:

  1. A new resource group, for example oatmilk, and your region.
  2. Image: Ubuntu Server 24.04 LTS. Size: Standard_B2ms (2 vCPUs, 8 GB) or larger.
  3. Authentication: an SSH public key.
  4. Inbound ports: SSH (22), HTTP (80) and HTTPS (443).
  5. Disks: a 64 GB Premium SSD OS disk. Files are kept here unless you choose a bucket.
  6. Networking: a new virtual network with the default subnet, and a Static public IP.

2. Create the database

In Azure Database for PostgreSQL flexible servers › Create:

  1. The same resource group and region. PostgreSQL version: 17.
  2. Compute + storage: Burstable B2s for a trial, General Purpose for a team.
  3. Authentication: PostgreSQL authentication only. Set an admin name, for example oatmilk_admin, and a password.
  4. Networking: Private access (VNet integration), in the VM's virtual network, on a new subnet for the database.

When it is deployed:

  1. In Server parameters, find azure.extensions and allow PGCRYPTO, UUID-OSSP and PG_STAT_STATEMENTS. Save. Oatmilk's database needs the first two; Azure refuses extensions that aren't on this list.
  2. In Databases, add oatmilk.
  3. Copy the Server name from Overview.
Text
postgres://oatmilk_admin:<password>@<server name>:5432/oatmilk?sslmode=require

Encode special characters in the password for a URL (@ is %40), or use letters and digits.

3. Choose where Redis runs

Redis holds caches, rate limits and locks, so the simplest choice is the bundled Redis on the VM: answer Run one here in setup.

For a managed one, create an Azure Cache for Redis in the same region, then copy its host name and Primary access key from Authentication. Use its TLS port:

Text
rediss://:<access key>@<name>.redis.cache.windows.net:6380

4. Choose where files go

  • On the VM (the simplest): answer On this machine in setup. Files live in a Docker volume on the VM's disk. Back them up with bun run self-host backup and the VM's disk backups.
  • In an S3-compatible store such as Cloudflare R2 or a MinIO you run: see Bring your own services.

5. Point your domain at it

In DNS zones, or at your DNS provider, add an A record for books.example.com with the VM's public IP. Wait until dig +short books.example.com answers with it.

6. Install Oatmilk on the VM

SSH in, then:

Shell
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker "$USER" && newgrp docker
curl -fsSL https://bun.sh/install | bash && source ~/.bashrc
git clone https://github.com/AGI-Ventures-Canada/oatmilk.git && cd oatmilk
bun install
bun run self-host setup
QuestionAnswer
Where will Oatmilk run?On a server, at books.example.com
Which PostgreSQL database?Use one I already have, then the connection string
Which Redis?Run one here, or Use one I already have with the Azure Cache address
Where should files be kept?On this machine, or your S3-compatible store
How should people sign in?Accounts kept here, with sign-up Only people I add or invite
Which AI models…?Vercel AI Gateway, or a model server in your network as Another OpenAI-compatible server
Should Oatmilk send email?Yes, with Resend

Or, without questions:

Shell
bun run self-host init --server --domain books.example.com \
  --database-url 'postgres://oatmilk_admin:…@<server name>:5432/oatmilk?sslmode=require'
bun run self-host up

7. Check it and sign in

Shell
bun run self-host doctor
bun run self-host logs db-init migrate   # if preparing the database failed

Open https://books.example.com, sign in with the account setup made, and create your company.

Back up

  • Database: the flexible server's automated backups and point-in-time restore.
  • Files on the VM: bun run self-host backup on a schedule, copied off the VM, and Azure Backup for the VM's disk.
  • Settings: keep a copy of self-host/.env somewhere safe. It holds the keys that decrypt connector credentials and contractor details.

If something goes wrong

ProblemFix
db-init says an extension isn't allow-listedAdd PGCRYPTO and UUID-OSSP to azure.extensions, save, then bun run self-host up again.
The database steps can't connectCheck that the database is in the VM's virtual network, and that the string ends in ?sslmode=require.
The app can't reach Azure Cache for RedisUse rediss:// and port 6380, with the access key as the password.