Self-hosting
Deploy on Google Cloud
Run Oatmilk on Compute Engine with Cloud SQL, Memorystore and Cloud Storage, step by step.
This guide runs Oatmilk on one Compute Engine VM, with its data in Google Cloud's managed services: Cloud SQL for PostgreSQL, Memorystore for Redis and Cloud Storage for files. Do every step in the same project and region, for example northamerica-northeast1.
1. Reserve an address and open the web ports
- In VPC network › IP addresses, reserve a static external IPv4 address in your region.
- In VPC network › Firewall, create a rule on the
defaultnetwork: ingress, target tagoatmilk, source0.0.0.0/0, TCP ports80and443.
2. Create the database
In SQL › Create instance › PostgreSQL:
- Database version: PostgreSQL 17. Set a password for the
postgresuser and keep it. - Machine: 2 vCPUs or more, with automated backups on.
- Connections: Private IP on the
defaultnetwork. If it asks, set up the private services connection. Turn Public IP off.
When it is ready:
- In Databases, create
oatmilk. - Copy the instance's private IP address.
postgres://postgres:<password>@<private IP>:5432/oatmilk?sslmode=requireEncode special characters in the password for a URL (@ is %40), or use letters and digits.
3. Create Redis
In Memorystore › Redis › Create instance:
- Tier: Basic (or Standard for a replica), 1 GB, in your region.
- Network:
default. - Turn AUTH on. Leave in-transit encryption off: the VM reaches Redis only on Google's private network.
When it is ready, copy its IP address and its AUTH string.
redis://:<auth string>@<IP address>:63794. Create the bucket for files
Cloud Storage serves an S3-compatible API, which Oatmilk's file storage uses.
- In Cloud Storage › Buckets, create one, for example
acme-oatmilk-files, in your region, with uniform access and public access prevention on. - In IAM & Admin › Service accounts, create
oatmilk-storage. In the bucket's Permissions, give it Storage Object Admin. - In Cloud Storage › Settings › Interoperability, create an HMAC key for that service account. Copy the access ID and secret.
5. Create the VM
In Compute Engine › VM instances › Create instance:
- Machine:
e2-standard-2(2 vCPUs, 8 GB) or larger, in your region. - Boot disk: Ubuntu 24.04 LTS, 40 GB.
- Networking: network tag
oatmilk, and the static address from step 1 as its external IP.
6. Point your domain at it
In Cloud DNS, or at your DNS provider, add an A record for books.example.com with the static address. Wait until dig +short books.example.com answers with it.
7. Install Oatmilk on the VM
Connect with SSH from the console, 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 Cloud SQL connection string |
| Which Redis? | Use one I already have, then the Memorystore address |
| Where should files be kept? | In an S3-compatible bucket: the bucket name, region auto, endpoint https://storage.googleapis.com, the HMAC access ID and secret, and Yes to path-style addresses |
| 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:
export OATMILK_S3_ACCESS_KEY_ID=GOOG…
export OATMILK_S3_SECRET_ACCESS_KEY=…
bun run self-host init --server --domain books.example.com \
--database-url 'postgres://postgres:…@10.0.0.3:5432/oatmilk?sslmode=require' \
--redis-url 'redis://:…@10.0.0.4:6379' \
--s3-bucket acme-oatmilk-files --s3-region auto --s3-endpoint https://storage.googleapis.com --s3-force-path-style
bun run self-host up8. 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. Upload a receipt to check that files reach the bucket.
Back up
- Database: Cloud SQL's automated backups and point-in-time recovery.
- Files: turn on Object versioning or soft delete on the bucket.
- 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 |
|---|---|
| The database steps can't connect | Check that Cloud SQL has a private IP on the VM's network, and that the string ends in ?sslmode=require. |
| The app can't reach Redis | Check the AUTH string and that Memorystore is on the default network. |
| Uploads fail with a signature error | Check endpoint https://storage.googleapis.com, region auto and path-style addresses in self-host/.env. |