#Image Command
Build a container image from ./dist.
#Quick Start
alepha build
alepha image --tag
# → <image.tag>:latest
⚠️ It needs the docker CLI. It shells out to docker build, so it is a local and CI command and cannot run in an in-process build path.
#A Dockerfile you own
No Dockerfile in the app directory: one is generated there, beside alepha.config.ts. One already present: it is used untouched.
⚠️ It lands in the app directory, not in dist/, and that is not a preference: alepha build wipes dist/ on every run, so a file living there could never be committed or edited. The build context is still dist/, so the Dockerfile's COPY . . keeps meaning the built output rather than dragging src/ and node_modules/ in.
The generated header records the manifest facts it came from:
# Generated by `alepha image`, from this build's manifest:
# runtime: node
# entry: index.node.js
# It is yours now. Edit it freely; `alepha image` reuses it untouched
# and only warns when the facts above stop matching a later build.
A later run warns when they no longer match, and never fails. The file is yours after generation, so a hard failure would make an intentional edit feel like a bug; the warning exists only so the drift is not silent.
#Which slice it runs
The primary: the first declared runtime, read from the manifest. An image runs one process from one entry point, and this is the same rule every deployer follows.
A static artifact is refused (it runs no process, so there is nothing to start) and so is a workerd one (only Cloudflare runs it).
#A binary and nothing else
alepha image --compile --tag
Compiles the bun slice first and ships an image holding the binary, on gcr.io/distroless/cc-debian12. Measured on a real app: 197 MB against 279 MB, and 49 MB against 67 MB compressed.
#⚠️ The libc, which is where this goes wrong
A Bun --compile binary is not static, under any triple:
$ file dist/app
ELF 64-bit LSB executable, dynamically linked,
interpreter /lib/ld-musl-aarch64.so.1
$ ldd dist/app
Error loading shared library libstdc++.so.6: No such file or directory
Error loading shared library libgcc_s.so.1: No such file or directory
It needs an interpreter, libstdc++ and libgcc. Put it on a base that has none and you do not get a build failure: you get exec /app/app: no such file or directory, naming a file that is plainly there, from a container that simply stops.
So alepha image derives the triple from the base rather than inheriting whatever the host would default to, and refuses a base with no libc (scratch, distroless/static) rather than building one, because no triple fixes it.
The compiled variant stays root: the base has no shell, so a declared volume cannot be prepared at build time.
#Options
| Flag | Description |
|---|---|
--tag, -t |
--tag uses latest, --tag=1.3.4 that version with the configured name, --tag=other/img:v1 verbatim |
--compile, -c |
Compile the bun slice first and ship an image holding the binary. --compile name names it |
--dockerfile |
Write the Dockerfile and stop, without building an image |
Building needs a name: image.tag in the config, unless --tag is a complete name:tag. --tag=:1.3.4 is the same as --tag=1.3.4.
#Configuration
image: is a top-level key in alepha.config.ts, not build.docker: config follows the command that reads it.
1import { defineConfig } from "alepha/cli/config"; 2 3export default defineConfig({ 4 image: { 5 from: "node:26-alpine", 6 volumes: ["/data"], 7 env: { DATA_DIR: "/data" }, 8 tag: "ghcr.io/myorg/app", 9 oci: true,10 },11});
build.cloudflare correctly stays under build, because wrangler.jsonc really is written by the build.
These keys shape the generated Dockerfile. A Dockerfile you already own is used untouched, so they only reach it through a regeneration (delete it and run alepha image again). tag, args and the build labels of oci go on the docker build command, so they apply either way.
| Key | Default | Description |
|---|---|---|
from |
node:24-alpine, oven/bun:alpine for bun, gcr.io/distroless/cc-debian12 with --compile |
Base image (FROM) |
command |
node, or bun for bun |
Command that runs the entry. Not used with --compile, where the binary is the command |
install |
- | Packages installed with npm install before the app is copied, for CLI tools the app shells out to (e.g. wrangler). Ignored with --compile |
env |
- | ENV defaults, emitted after the built-in SERVER_HOST=0.0.0.0. Not secrets: docker run -e overrides them and docker inspect shows them |
volumes |
- | VOLUME mount points. Each is created and owned by the container user first, so a named volume starts writable |
user |
1000 (none with --compile) |
USER the process runs as. "root" opts back into root |
tag |
- | Image name without a version, completed by --tag |
args |
- | Extra docker build arguments, e.g. ["--platform", "linux/amd64"] |
oci |
- | Add the OCI labels: revision (git commit), created and version (from the tag) on the build command, plus the labels below |
labels |
- | source, title, description and licenses OCI labels, written into the Dockerfile when oci is on, each only when set. source links a package to its repository on GHCR |