Use Docker when your project already has a Dockerfile, needs system packages, or runs a language outside Bex's native build runtimes. Bex can build the image from Git or deploy an image you have already pushed to a registry.
Choose the source
| Source | Blueprint settings | What Bex does |
|---|---|---|
| Dockerfile in Git | runtime: docker, repo, branch | Builds your Dockerfile and deploys the resulting image. |
| Prebuilt image | runtime: image, image.url | Deploys the supplied image without a source build. |
The workload type is independent of the source: use web, pserv, worker,
or cron as appropriate. See Service types.
Build a Dockerfile
services:
- name: api
type: web
runtime: docker
repo: https://github.com/your-org/api
branch: main
dockerfilePath: ./Dockerfile
autoDeployTrigger: offReplace the repository URL with yours. Keep files needed by COPY inside the
build context and check any rootDir or Docker context settings for monorepos.
Your Dockerfile's default command should start the service. For Dockerfile
builds, dockerCommand can override the startup command; native runtime
buildCommand/startCommand settings are a different workflow.
Where caching is enabled, unchanged Dockerfile layers are reused from the service's cache; see Build caching. If a rebuild keeps serving stale layers, clear the cache for one deploy as described in How deploys work.
Inspect missing build files
Use the Docker build-context inspector when a
COPY or local ADD cannot find a file. Paste the Dockerfile, a repository-relative
file inventory, your build settings and the applicable ignore files. The tool
shows resolved paths, included/excluded entries, the winning ignore rule and
source-line findings. It runs entirely in your browser.
From a clean checkout of the commit you intend to build, this Python command lists paths without reading file contents. It includes hidden entries and empty directories, and records symlink targets without following them:
python3 - <<'PY' > /tmp/bex-build-files.txt
import os
from pathlib import Path
for base, directories, files in os.walk(".", followlinks=False):
for name in sorted(directories + files):
path = Path(base) / name
relative = path.as_posix()
if "\n" in relative or "\r" in relative:
raise SystemExit("Paths with newlines are not supported")
if path.is_symlink():
print(f"{relative} -> {os.readlink(path)}")
else:
print(relative + ("/" if path.is_dir() else ""))
PYPaste /tmp/bex-build-files.txt into the inventory input. Confirm completeness
only if it describes the actual build source: local generated or untracked files
may not exist in the Git checkout used by Bex. The inspector accepts at most
5,000 entries, including inferred directories; a filtered subset is not a complete
inventory. A Dockerfile-specific <Dockerfile name>.dockerignore replaces the
context-root .dockerignore, including when its contents are empty.
“Available” means a local source matches included inventory entries. It does not
prove the build succeeds. Symlinks, remote ADD, COPY --from, heredocs,
unknown variables and unsupported syntax stay explicitly unresolved. Compare
the monorepo context settings if a
file exists in the repository but lies outside the selected context.
Deploy an existing image
services:
- name: api
type: web
runtime: image
image:
url: ghcr.io/your-org/api:release-1
numInstances: 1Replace the image with one you can pull and run. Prefer an immutable release tag or digest so the manifest identifies the intended artifact. Follow the Blueprint guide to authenticate, validate, and apply either example. The examples deliberately omit a health-check path; configure one when your application provides a suitable endpoint.
Image-backed services are not affected by build caching: deploying a prebuilt image runs no build, so there is nothing to reuse and nothing to clear.
If the image runs today from a docker run command, paste it into the
docker run to Compose converter. It maps ports,
environment, volumes, networks and the restart policy into a
docker-compose.yml, lists every flag it does not translate, and hands the
file to the Compose → render.yaml converter. The command stays in your browser.
Use a private registry
Create a registry credential in the target workspace through the dashboard or
/v1/registrycredentials API. Creating a credential alone does not attach it
to a service. Select it in the service creation form, or reference its name in
the Blueprint:
services:
- name: private-api
type: web
runtime: image
image:
url: ghcr.io/your-org/private-api:release-1
creds:
fromRegistryCreds:
name: ghcr-readonlyghcr-readonly must match a registry credential available in that workspace.
For a Dockerfile that pulls private base images, use the service-level
registryCredential.fromRegistryCreds.name reference instead. Store access
tokens in the registry credential, not in the image URL or Git manifest.
Prepare the container for Bex
HTTP services must listen on 0.0.0.0:$PORT; Bex defaults to port 3000.
EXPOSE documents a port in an image but does not make the process listen or
configure Bex's service port. If the image uses a separate setting, make it
match the service listener. Bex injects PORT at runtime and drops a PORT
value set in the image, so an ENV PORT=… line never reaches the process. Use
an unprivileged port: tenant containers drop every Linux capability, so binding
a port below 1024 fails even as root and the container crash-loops. Stock
images that listen on port 80, such as nginx and httpd, need their listener
moved to $PORT.
Keep required state outside the disposable container filesystem. Use a managed datastore or a persistent disk, and handle termination signals so requests or jobs can finish safely during a deployment.
Dockerfile build contract
Bex builds a Dockerfile with BuildKit's stock Dockerfile frontend and runs the
image under a hardened container policy. The tables list where that differs
from what a Dockerfile can assume on a local Docker host. Paste a Dockerfile
into the Dockerfile checker to find these lines in
your own file; it runs in your browser. Source pin: Bex d95d017ba.
At build time
| Dockerfile | What Bex does | What to do |
|---|---|---|
ARG NAME without a default | Passes no --build-arg. Service environment variables are not build arguments, so the value is empty. | Give the ARG a default, or read the value at runtime. |
RUN --mount=type=secret | Passes no build secrets to Dockerfile builds. With required=true the build fails; otherwise the mount is empty. | Fetch the dependency another way, or move the step to runtime. |
RUN --mount=type=cache | Starts the mount empty on every build; only image layers are cached. See Build caching. | Nothing breaks; expect the download on each build. |
At run time
| Dockerfile | What Bex does | What to do |
|---|---|---|
HEALTHCHECK | Does not run it. Bex probes the service with a TCP connection to the service port, or an HTTP GET to healthCheckPath when one is set. See Health checks. | Set healthCheckPath to an endpoint the app serves. |
su, gosu, su-exec or setpriv in the start command, from root | Allows the container to run as root, but by default drops every Linux capability, so switching to another user fails with "operation not permitted". | Set the user with USER in the Dockerfile. |
sudo or another setuid program, from a non-root user | Disables privilege escalation for every service, so setuid programs cannot raise privileges. | Do the privileged work at build time. |
Final stage FROM --platform= other than linux/amd64 | Runs app nodes on linux/amd64 only, so the container will not start. | Drop the flag or set it to linux/amd64. |
FROM scratch or a distroless final stage, with a dockerCommand override | Runs the override as /bin/sh -c "<command>", which fails in an image without a shell. The image's own CMD and ENTRYPOINT run unchanged. | Keep the start command in the image. |
Shell-form CMD or ENTRYPOINT | Runs /bin/sh as PID 1, which may not pass SIGTERM on. On stop, Bex sends SIGTERM and by default kills the container 30 seconds later; web and private services without a persistent disk first get a 10-second drain. See HTTP shutdown drain. | Use the exec form (CMD ["node", "server.js"]), or start the program with exec. |
Update an image
For a Blueprint-managed service, update image.url, validate, and apply the
manifest through Bex. Follow the resulting deployment until it completes.
Updating a source setting and starting a deploy are distinct API operations;
when changing the image through service settings, use the deployment action
to release the saved source.
Changing an upstream mutable tag does not by itself submit a Bex deployment. For a predictable release, publish a new versioned image and deploy that reference. To promote the exact image CI tested (immutable digest) from GitHub Actions, see CI deploys a tested image digest. Avoid patching API-managed Kubernetes resources directly: the control plane owns their desired configuration.
Troubleshoot
Check build logs for Dockerfile/context errors. For image pull failures, verify
the image name, tag or digest, registry access, and attached credential. If the
image pulls but the process fails, inspect runtime logs, architecture support,
its startup command, listener port, and required environment variables. Bex
app nodes run linux/amd64, so an image built only for arm64 will not start;
see the Dockerfile build contract. If a
Dockerfile rebuild at the right commit still serves old behavior, suspect
stale cached layers and work through My change did not ship.
See Web services, Secrets, and Rollbacks.