Self-hosting
Deploy on Azure
Run Oatmilk on an Azure VM with Azure Database for PostgreSQL and Azure Cache for Redis, step by step.
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:
- A new resource group, for example
oatmilk, and your region. - Image: Ubuntu Server 24.04 LTS. Size:
Standard_B2ms(2 vCPUs, 8 GB) or larger. - Authentication: an SSH public key.
- Inbound ports: SSH (22), HTTP (80) and HTTPS (443).
- Disks: a 64 GB Premium SSD OS disk. Files are kept here unless you choose a bucket.
- 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:
- The same resource group and region. PostgreSQL version: 17.
- Compute + storage: Burstable
B2sfor a trial, General Purpose for a team. - Authentication: PostgreSQL authentication only. Set an admin name, for example
oatmilk_admin, and a password. - Networking: Private access (VNet integration), in the VM's virtual network, on a new subnet for the database.
When it is deployed:
- In Server parameters, find
azure.extensionsand allowPGCRYPTO,UUID-OSSPandPG_STAT_STATEMENTS. Save. Oatmilk's database needs the first two; Azure refuses extensions that aren't on this list. - In Databases, add
oatmilk. - Copy the Server name from Overview.
postgres://oatmilk_admin:<password>@<server name>:5432/oatmilk?sslmode=requireEncode 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:
rediss://:<access key>@<name>.redis.cache.windows.net:63804. 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 backupand 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:
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| Question | Answer |
|---|---|
| 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:
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 up7. Check it and sign in
bun run self-host doctor
bun run self-host logs db-init migrate # if preparing the database failedOpen 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 backupon a schedule, copied off the VM, and Azure Backup for the VM's disk. - Settings: keep a copy of
self-host/.envsomewhere safe. It holds the keys that decrypt connector credentials and contractor details.
If something goes wrong
| Problem | Fix |
|---|---|
db-init says an extension isn't allow-listed | Add PGCRYPTO and UUID-OSSP to azure.extensions, save, then bun run self-host up again. |
| The database steps can't connect | Check 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 Redis | Use rediss:// and port 6380, with the access key as the password. |