Self-hosting

Deploy on Google Cloud

Run Oatmilk on Compute Engine with Cloud SQL, Memorystore and Cloud Storage, step by step.

60 minutes · Advanced

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

  1. In VPC network › IP addresses, reserve a static external IPv4 address in your region.
  2. In VPC network › Firewall, create a rule on the default network: ingress, target tag oatmilk, source 0.0.0.0/0, TCP ports 80 and 443.

2. Create the database

In SQL › Create instance › PostgreSQL:

  1. Database version: PostgreSQL 17. Set a password for the postgres user and keep it.
  2. Machine: 2 vCPUs or more, with automated backups on.
  3. Connections: Private IP on the default network. If it asks, set up the private services connection. Turn Public IP off.

When it is ready:

  1. In Databases, create oatmilk.
  2. Copy the instance's private IP address.
Text
postgres://postgres:<password>@<private IP>:5432/oatmilk?sslmode=require

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

3. Create Redis

In Memorystore › Redis › Create instance:

  1. Tier: Basic (or Standard for a replica), 1 GB, in your region.
  2. Network: default.
  3. 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.

Text
redis://:<auth string>@<IP address>:6379

4. Create the bucket for files

Cloud Storage serves an S3-compatible API, which Oatmilk's file storage uses.

  1. In Cloud Storage › Buckets, create one, for example acme-oatmilk-files, in your region, with uniform access and public access prevention on.
  2. In IAM & Admin › Service accounts, create oatmilk-storage. In the bucket's Permissions, give it Storage Object Admin.
  3. 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:

  1. Machine: e2-standard-2 (2 vCPUs, 8 GB) or larger, in your region.
  2. Boot disk: Ubuntu 24.04 LTS, 40 GB.
  3. 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:

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 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:

Shell
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 up

8. 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. 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/.env somewhere safe. It holds the keys that decrypt connector credentials and contractor details.

If something goes wrong

ProblemFix
The database steps can't connectCheck 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 RedisCheck the AUTH string and that Memorystore is on the default network.
Uploads fail with a signature errorCheck endpoint https://storage.googleapis.com, region auto and path-style addresses in self-host/.env.