Deploy an App from a Git Repository
FoundryDB can build your app from source. You give it a repository, it builds an OCI image on your app's own VM, pushes that image to the platform registry, and deploys it behind HTTPS. You never build or push an image yourself, and you never run a registry.
In this tutorial you deploy a repository, watch the build, rebuild it, and then wire up push-to-deploy so a git push ships the change on its own.
Prerequisites
- A FoundryDB account and API credentials (
FOUNDRYDB_USER,FOUNDRYDB_PASSWORD). - A git repository the platform can reach. A public HTTPS URL needs nothing extra; a private repository needs a deploy key, covered below.
- The repository either has a
Dockerfileor is a language the buildpacks understand. You do not have to choose: detection is automatic.
1. Deploy the repository
curl -u "$FOUNDRYDB_USER:$FOUNDRYDB_PASSWORD" -X POST https://api.foundrydb.com/app-services \
-H "Content-Type: application/json" \
-d '{
"name": "my-api",
"plan_name": "tier-2",
"zone": "se-sto1",
"app_config": {
"source": {
"type": "git",
"repo_url": "https://github.com/acme/api.git",
"ref": "main"
},
"container_port": 8080
}
}'
Two fields deserve attention.
container_port is the port your process listens on inside the container. It is not the port the world uses: public traffic arrives on HTTPS at 443 and the ingress forwards it to this port. Get it wrong and the app builds and starts but never becomes healthy, because the readiness probe has nothing to talk to.
There is no image_ref. For a source-built app the image is the build output: each successful build writes a digest reference such as registry.foundrydb.com/apps/{id}@sha256:... into the app's configuration, and the deploy path consumes it. Supplying your own would be a contradiction.
2. Watch it build
The app moves through ProvisioningVM → BuildingApp → DeployingApp → Running. A small app is typically running a couple of minutes after the call returns.
curl -su "$FOUNDRYDB_USER:$FOUNDRYDB_PASSWORD" \
https://api.foundrydb.com/app-services/$APP_ID | jq -r .status
Every attempt is recorded as a build, whether it succeeds or fails:
curl -su "$FOUNDRYDB_USER:$FOUNDRYDB_PASSWORD" \
https://api.foundrydb.com/app-services/$APP_ID/builds | jq '.builds[0]'
To follow a build while it runs, poll its log with the cursor the previous response returned. The first call omits offset; each later call passes back the next_offset it was given, so you receive only what has arrived since:
curl -su "$FOUNDRYDB_USER:$FOUNDRYDB_PASSWORD" \
"https://api.foundrydb.com/app-services/$APP_ID/builds/$BUILD_ID/logs?offset=$OFFSET"
The response also carries steps: an ordered record of each phase (fetch source, resolve commit, build, push, resolve digest, clean up). When a build fails, the failing step names the phase and carries the tail of the builder's own output, which is usually the whole answer.
3. Dockerfile or buildpacks
Left alone, the builder decides: it uses your Dockerfile if the repository has one, and Cloud Native Buildpacks if it does not. You can force either with source.builder:
builder | Behaviour |
|---|---|
| omitted | Dockerfile when present, otherwise buildpacks |
dockerfile | Always your Dockerfile; fails clearly if there is none |
buildpacks | Always buildpacks, ignoring any Dockerfile |
Prefer a Dockerfile when you care exactly what is in the image, need a specific base, or already have one that works. Prefer buildpacks when you would rather not maintain one: they detect the language, bring a runtime, and produce a reasonable image with no build file at all.
Rebuilds reuse cached layers, so a second build of unchanged source is markedly faster than the first.
4. Rebuild
To rebuild the current source, for example after pushing a commit:
curl -su "$FOUNDRYDB_USER:$FOUNDRYDB_PASSWORD" -X POST \
https://api.foundrydb.com/app-services/$APP_ID/builds
A rebuild is a normal blue/green deploy. The new container has to pass its health check before any traffic moves to it, so a build that produces a broken image leaves the previous version serving.
If a build is already running, the request is not refused and not queued behind it as a second build. It is recorded, and one more build runs when the current one finishes. Ten pushes during one long build produce one rebuild of the newest source, not ten builds of commits that were superseded before they finished.
5. Private repositories
Supply an SSH deploy key and an SSH-form repo_url:
{
"source": {
"type": "git",
"repo_url": "git@github.com:acme/api.git",
"ref": "main",
"deploy_key": "-----BEGIN OPENSSH PRIVATE KEY-----\n...\n-----END OPENSSH PRIVATE KEY-----\n"
}
}
The key is write-only: it is never returned by any read, and it is preserved when you omit it on a later update as long as repo_url is unchanged. Changing the repository clears it, so a key is never carried onto a repository it cannot authenticate.
Use a read-only deploy key scoped to that one repository. Do not put a token in the URL (https://user:token@github.com/...): that form is rejected, because the URL is recorded on every build and returned by the builds API.
6. Push to deploy
Mint a webhook secret:
curl -su "$FOUNDRYDB_USER:$FOUNDRYDB_PASSWORD" -X POST \
https://api.foundrydb.com/app-services/$APP_ID/webhook-secret
The response carries the webhook_url, the secret, and the content type to configure. The secret is shown once and never again; minting a new one replaces the old, which is also how you revoke a leaked one.
Add that URL and secret as a webhook on your git host, then enable auto-deploy:
curl -su "$FOUNDRYDB_USER:$FOUNDRYDB_PASSWORD" -X PUT \
https://api.foundrydb.com/app-services/$APP_ID/build-settings \
-H "Content-Type: application/json" \
-d '{"repo_url":"git@github.com:acme/api.git","ref":"main","auto_deploy":true}'
Now a push to the tracked ref rebuilds and redeploys. Pushes to other branches are acknowledged and ignored, and so is a branch deletion: there is nothing to build, and building the previous head would deploy code the author just removed.
The webhook endpoint cannot be authenticated the usual way, because a git host presents no credentials. The signature is the authentication: every request is verified against your secret before anything happens, and an unverified request is indistinguishable from an attacker's.
7. Deploying without a repository
If the code is not somewhere the platform can clone from, upload it instead. Pack the source under a single top-level directory, which the builder strips so your Dockerfile lands at the build root:
tar czf src.tar.gz my-app/
curl -su "$FOUNDRYDB_USER:$FOUNDRYDB_PASSWORD" -X POST \
https://api.foundrydb.com/app-source-uploads \
-H "Content-Type: application/octet-stream" \
--data-binary @src.tar.gz
Create the app with source.type upload and the returned upload_ref. Compare the returned checksum_sha256 against your own before continuing; the builder verifies the same checksum after fetching, so a corrupted transfer fails with that stated cause rather than as a confusing tar error later.
Uploads expire 24 hours after creation, so create the app in the same session. There is no repository to watch, so new code means uploading again: use a git source if you want pushes to deploy on their own.
What to remember
- The image is the build output. You supply source; the platform supplies the registry.
container_portis your process's port, not the public one.- A failed build never disturbs the version currently serving.
- A rebuild requested mid-build is coalesced, not dropped and not multiplied.
- The webhook secret is shown once; re-minting is how you rotate it.
Next steps
- Build from Source for the full field reference, build settings, statistics and retention.
- Attachments to give the app a database, object storage, or another app over private networking.
- Custom Domains to serve it on your own hostname with an automatic certificate.