Skip to main content

Private Registries

Public images need no credentials. To deploy an image from a private registry, supply registry_username and registry_password in app_config. The registry host is derived from image_ref, so you only provide the username and credential.

Pull from a private registry

curl -u "$USER:$PASS" -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": {
"image_ref": "ghcr.io/acme/api:1.0.0",
"container_port": 8080,
"registry_username": "acme-bot",
"registry_password": "ghp_XXXXXXXXXXXXXXXXXX"
}
}'

The platform authenticates the pull using these credentials on the VM. For GitHub Container Registry (ghcr.io) the password is a personal access token or a fine-grained token scoped to the repository. For Docker Hub (docker.io) it is your Docker Hub password or an access token. For other registries, the username and password are whatever the registry expects for its docker login equivalent.

How pull authentication works

When the platform deploys your app, the agent running on the VM:

  1. Writes an OCI-format auth file (equivalent to ~/.docker/config.json) containing the registry host, username, and password.
  2. Passes that auth file to the container runtime when pulling the image.
  3. Removes the auth file from the working directory after the pull completes.

The credential travels from the controller to the agent over the encrypted internal task channel. It is never logged, never returned in any API response, and not stored in plaintext on disk.

Credential handling

registry_password is write-only: the API never returns it in any response. The credential travels to the VM over the encrypted internal task channel, where the agent writes an OCI auth file before pulling. It is not logged or stored in plaintext.

On a redeploy (via PATCH), you can omit registry_password to keep the existing credential:

curl -u "$USER:$PASS" -X PATCH https://api.foundrydb.com/app-services/{id} \
-H "Content-Type: application/json" \
-d '{
"app_config": {
"image_ref": "ghcr.io/acme/api:1.1.0",
"container_port": 8080,
"registry_username": "acme-bot"
}
}'

The stored password is reused as long as registry_username is unchanged. If you change the username, you must also supply the new password.

On rollback, the same rule applies: the current stored password is reused when the rolled-back revision's registry_username matches the current one.

Rotating credentials

If you rotate the registry credential (for example, revoke and reissue a GitHub personal access token), supply the new password on the next PATCH:

curl -u "$USER:$PASS" -X PATCH https://api.foundrydb.com/app-services/{id} \
-H "Content-Type: application/json" \
-d '{
"app_config": {
"image_ref": "ghcr.io/acme/api:1.1.0",
"container_port": 8080,
"registry_username": "acme-bot",
"registry_password": "ghp_NEW_TOKEN_HERE"
}
}'

This triggers a redeploy that pulls the image with the new credential. If the old credential was already revoked, include the new password; otherwise the pull will fail at start-container-green and the previous revision continues serving.

For AWS ECR, the temporary password expires every 12 hours. A PATCH that includes a freshly obtained ECR password is required before each redeploy.

Common registries

Registryimage_ref prefixNotes
GitHub Container Registryghcr.io/org/image:tagUse a personal access token with read:packages scope.
Docker Hub (private)docker.io/org/image:tagUse a Docker Hub access token (not your account password).
GitLab Container Registryregistry.gitlab.com/group/project:tagUse a deploy token or personal access token with read_registry scope.
AWS ECR123456789.dkr.ecr.eu-west-1.amazonaws.com/image:tagUse an IAM access key ID as username and the temporary ECR password as the password. Note ECR passwords expire every 12 hours; a PATCH is required to refresh.
CustomAny hostname in the image_refThe registry host is derived from the first segment of image_ref.

Troubleshooting pull failures

A failed pull surfaces in the deploy log as a failed step at start-container-green. The message field will indicate an authentication error (401 Unauthorized) or image-not-found error (404). The most common causes:

  • Wrong token scope. For ghcr.io, the token must have read:packages. For GitLab, the deploy token must have read_registry.
  • Username mismatch. The registry_username must be the account or bot that owns the token, not the organization name.
  • Expired credential. AWS ECR tokens expire after 12 hours. Other registries may issue short-lived tokens as well.
  • Image tag not found. The tag in image_ref does not exist in the registry, or the repository is private and the credential does not have access.