Skip to main content

Custom Domains and Private Registries for FoundryDB Apps

· 5 min read
FoundryDB Team
Engineering @ FoundryDB

App Hosting launched with apps served at https://{name}.foundrydb.com from public images. That covers the demo and the internal tool. It does not cover the two things every real deployment needs: your app on your domain, and your app built from a private image. Both shipped alongside the launch, and both are designed so you never hand a plaintext secret around or click through a certificate wizard.

This post covers the two-certificate model behind custom domains and the write-only credential model behind private registries.

Custom domains: two certificates, one app

By default an app serves on its primary {name}.foundrydb.com hostname. You can add up to five custom domains so the same app also answers on names you own, for example api.acme.com. The app keeps its primary domain; custom domains are additive.

curl -u "$USER:$PASS" -X PATCH https://api.foundrydb.com/app-services/APP_ID \
-H 'Content-Type: application/json' \
-d '{
"app_config": {
"custom_domains": ["api.acme.com", "orders.acme.com"]
}
}'

The thing worth understanding is that the primary domain and the custom domains get their certificates through two different challenge types, and that difference is not an implementation detail -- it changes what you have to do.

The primary {name}.foundrydb.com domain is on a zone the platform controls. Its certificate is issued over DNS-01, which means the platform can prove ownership by writing a DNS record itself, before your app serves a single request. There is nothing for you to do, and it works even before any traffic arrives.

A custom domain is on a zone you control, so the platform cannot write DNS records for it. Instead Caddy obtains the certificate over HTTP-01: it answers a challenge on the live HTTPS ingress as soon as the domain actually points at your app. Your job is the pointing.

Pointing your DNS

You have two ways to point a custom domain at the app, and the choice is the usual CNAME-versus-A tradeoff.

  • CNAME to the app's primary domain ({name}.foundrydb.com). This is the recommended path. If the underlying address ever changes, your record keeps working because it tracks the primary domain, not a fixed IP.
  • A record to the app's floating IP. Use this when the hostname has to be an apex domain that cannot be a CNAME.

Once the domain resolves to the app, Caddy completes the HTTP-01 challenge automatically and starts serving HTTPS on the custom domain. There is no certificate to upload, renew, or babysit. Renewal is automatic for the lifetime of the app.

Private registries: deploy images nobody else can pull

Plenty of teams keep their application images private, on ghcr.io, on a registry behind their org, on whatever. App Hosting deploys those images directly. You provide a username and password for the registry, and the platform uses them to pull the image when it provisions or redeploys the app.

curl -u "$USER:$PASS" -X POST https://api.foundrydb.com/app-services \
-H 'Content-Type: application/json' \
-d '{
"name": "orders-api",
"plan_name": "tier-2",
"zone": "se-sto1",
"app_config": {
"image_ref": "ghcr.io/acme/orders-api:1.4.0",
"container_port": 8080,
"registry_username": "acme-bot",
"registry_password": "ghp_xxxxxxxxxxxxxxxxxxxx"
}
}'

You do not specify a registry host. The platform derives it from the image reference, so ghcr.io/acme/orders-api:1.4.0 authenticates against ghcr.io and a reference on another registry authenticates against that one. One less field to get wrong.

How the credential is handled

The registry password is treated as a write-only secret, and that shapes its whole lifecycle:

  • Never returned. The API never reads the password back. A GET on the app service shows you the username and the image reference, never the password. You cannot accidentally log it, screenshot it, or leak it through the API.
  • Encrypted in transit to the VM. The password is delivered to the VM that pulls the image over an encrypted channel, not stored in plaintext where it does not need to be.
  • Preserved across redeploys. Because the API never returns the password, a redeploy that does not include a new one keeps using the stored credential. You do not have to re-send the secret every time you ship a new image tag.
# Redeploy a new tag without re-sending the registry password
curl -u "$USER:$PASS" -X POST https://api.foundrydb.com/app-services/APP_ID/redeploy \
-H 'Content-Type: application/json' \
-d '{ "image_ref": "ghcr.io/acme/orders-api:1.5.0" }'

What we validated

This is not a paper feature. In the validation run an app was created from a private ghcr.io image with registry credentials, pulled the image, and reached Running. The negative case matters just as much: an app pointed at the same private image without credentials had its pull rejected. The credential is load-bearing, exactly as it should be, and the failure is loud rather than a silent fallback to some weaker behavior.

Putting it together

A real deployment uses both features at once: a private image pulled with stored credentials, served on your own domain with an automatically issued certificate, attached to your managed databases over a private network. The full request is not much bigger than the examples above.

curl -u "$USER:$PASS" -X POST https://api.foundrydb.com/app-services \
-H 'Content-Type: application/json' \
-d '{
"name": "orders-api",
"plan_name": "tier-2",
"zone": "se-sto1",
"app_config": {
"image_ref": "ghcr.io/acme/orders-api:1.4.0",
"container_port": 8080,
"custom_domains": ["api.acme.com"],
"registry_username": "acme-bot",
"registry_password": "ghp_xxxxxxxxxxxxxxxxxxxx"
},
"attached_service_ids": ["PG_SERVICE_ID"]
}'

Point api.acme.com at the app with a CNAME, wait for the app to reach Running, and you have a private image serving on your own domain with HTTPS, talking to your PostgreSQL service over a private network, with no secret ever leaving your control in plaintext and no certificate you ever had to think about.

Read the App Hosting guide for the full reference on custom domains and registry authentication, or drive it all from the SDKs and the MCP server. Bring your own domain and your own images. The platform handles the rest.