Deploy on Oracle Cloud
Single node on an OCI Compute instance with OCI Database with PostgreSQL: the resources to create, the settings that matter, and the exact commands, step by step.
On this page
This is the one-node deployment on Oracle Cloud Infrastructure: one Compute instance running the image, one OCI Database with PostgreSQL system, and one Load Balancer terminating TLS. Read the one-node page first. This page gives only what differs on OCI: the resources, where each step runs, and the OCI commands. Everything else, including the render flags, the secrets and the verification checklist, is the same and is not repeated here. The three-region layout later adds nodes and balancers to this same base.
Resources
| OCI resource | Settings |
|---|---|
| VCN | a public subnet (load balancer) and a private subnet (compute and database), with a NAT gateway on the private subnet for outbound HTTPS |
| Compute instance | VM.Standard.E4.Flex, 2 OCPU / 8 GB, Oracle Linux 9 or Ubuntu 22.04, private subnet, Docker Engine and Compose v2 installed |
| Database with PostgreSQL | version 16, one primary node (add read replicas later, for DR only), private endpoint in the private subnet, uuid-ossp and pgcrypto enabled on the DB system |
| Load Balancer | flexible shape, public subnet, TLS listener on 443 with your certificate, backend set → compute instance port 8080 |
| Vault | one secret holding secrets.json (base64 of the file) |
| Security lists | LB → compute 8080; compute → database 5432; nothing else inbound. Ports 5000–5999 and 9000–9999 on the compute instance stay closed |
Outbound through the NAT gateway, all on 443: ghcr.io,
aiplatform.googleapis.com and oauth2.googleapis.com (Vertex AI serves
every Claude request), and api.anthropic.com (the failover).
1. Database
Create the DB system with PostgreSQL 16. Before anything else, open
Configuration → Extensions on the DB system and enable uuid-ossp and
pgcrypto. An extension that is not enabled at the system level cannot be
created inside the database, and provision.sql then fails.
The endpoint is private, so run provision.sql from the compute instance as
the DB-system admin user, exactly as in step 2 of the one-node page, against
<db-private-fqdn>. The OCI certificate chains to a public root, so
sslmode=require works without an extra CA file.
2. Compute instance
sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin # Oracle Linux 9
sudo systemctl enable --now docker && sudo usermod -aG docker "$USER"
Then clone the bundle and log in to the registry as in step 1 of the one-node page.
3. Secrets and profile, in Vault
Run render-profile.sh once, on the compute instance or an operator machine,
with the flags from step 3 of the one-node page. One value differs:
--trusted-proxies is the CIDR of the load balancer's subnet (for
example 10.0.0.0/24), not 127.0.0.0/8.
Store the rendered secrets.json in Vault and place both files on the
instance:
oci vault secret create-base64 --region <region> --compartment-id ... --vault-id ... --key-id ... \
--secret-name dynapps-secrets --secret-content-content "$(base64 -w0 profile/secrets.json)"
sudo install -d -m 0750 -o 1000 -g 1000 /etc/dynapps/profile
sudo install -m 0644 -o 1000 -g 1000 profile/profile.yaml /etc/dynapps/profile/profile.yaml
oci secrets secret-bundle get --region <region> --secret-id <ocid> --query 'data."secret-bundle-content".content' --raw-output \
| base64 -d | sudo install -m 0600 -o 1000 -g 1000 /dev/stdin /etc/dynapps/profile/secrets.json
Two OCI CLI traps:
- Pass
--regionon everyocicommand (for exampleeu-frankfurt-1). Without it the CLI uses the active profile's region, which resolves to a different tenancy endpoint and fails with a not-found or auth error even though the OCID is correct. --secret-content-contenttakes the base64 value itself, not a file reference. The OCI CLI rejectsfile:///dev/stdin. On macOS, usebase64with no flags in place ofbase64 -w0.
The container runs as uid 1000 and mounts /etc/dynapps/profile read-only.
4. Boot
OCI uses the app-only compose file, because the database is external:
export SYSTEMPROMPT_TAG=0.67.0 DYNAPPS_PROFILE_DIR=/etc/dynapps/profile
export DATABASE_URL='postgres://systemprompt:<pw>@<db-private-fqdn>:5432/systemprompt?sslmode=require'
docker compose -f docker-compose.node.yml pull
docker compose -f docker-compose.node.yml up -d
docker compose -f docker-compose.node.yml ps # wait for: healthy
docker-compose.node.yml needs DATABASE_URL in the environment for its
readiness probe, and the same value inside secrets.json.
5. Load balancer
| Setting | Value |
|---|---|
| Listener | HTTPS 443, your certificate |
| Backend set | the compute instance, port 8080, round-robin policy |
| Health check | HTTP, path /api/v1/health, status 200, response body regex healthy. A status check alone admits a node that is still migrating, because it answers {"status":"starting"} |
| Headers | forward X-Forwarded-For and X-Forwarded-Proto; keep the Host header |
| Timeouts | idle timeout 300 s (streamed responses) |
| Rules | return 403 for /metrics and /api/v1/health/detail |
| Drain | when stopping the node, drain it in the backend set and wait 30 s before docker compose stop |
Point the DNS name you passed as --external-url at the load balancer.
6. Verify and sign in
Run verify-node.sh as in step 6 of the one-node page, with
docker-compose.node.yml in place of the single-node file, then check the
public address:
./verify-node.sh --url http://localhost:8080 \
--container "$(docker compose -f docker-compose.node.yml ps -q app)" --version 0.67.0
curl -fsS https://ai.example.com/api/v1/health
Every check must pass. Then configure Dynapps SSO
for this hostname and sign in at https://ai.example.com/admin/login.
Upgrades, backups, growth
- Upgrade. Drain the node at the load balancer, set
SYSTEMPROMPT_TAG=<new>, thenpullandup -d, runverify-node.sh, and re-enable the node. Migrations are forward-only, so take an OCI PostgreSQL backup first. - Backups. Enable automatic backups on the DB system, and test a restore once before launch.
- More nodes. Create a compute instance and a load balancer per region and
copy
/etc/dynapps/profileto it unchanged. Add each region's balancer subnet to--trusted-proxiesand re-render. The profile setsdatabase.migrate_on_boot: false: run the image's one-shotmigratecommand once, then start the nodes; they may start in parallel, because each replica claims its instance id under a database lock before registering its MCP servers. Every node uses the same primary; read replicas are for disaster recovery only. Node roles (server.role) and load shedding (server.max_in_flight) are covered in Deploy.