Bun Dockerfile for Production (Alpine, Non-Root bun User)

This Bun Dockerfile installs production dependencies with a frozen lockfile on oven/bun:1-alpine, then runs your TypeScript entry file directly with no compile step. The server runs as the image's non-root bun user on port 3000. It fits APIs written for Bun.serve or frameworks such as Hono and Elysia running on Bun.

CI status
Build passing
Image size (uncompressed)
127.4 MB
Build time
3 s
Runtime image
oven/bun:1-alpine
Port
3000
Last built
2026-10-15
Dockerfile
# syntax=docker/dockerfile:1
FROM oven/bun:1-alpine AS deps
WORKDIR /app
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile --production

FROM oven/bun:1-alpine
ENV NODE_ENV=production
WORKDIR /app
COPY --from=deps --chown=bun:bun /app/node_modules ./node_modules
COPY --chown=bun:bun . .
USER bun
EXPOSE 3000
CMD ["bun", "run", "index.ts"]
.dockerignore
.git
node_modules/
.env*
Dockerfile
.dockerignore

Before you build

How this Dockerfile works

  1. FROM oven/bun:1-alpine AS deps

    The official Bun image on Alpine. The 1 tag follows the latest Bun 1.x release, which the weekly CI rebuild keeps checking.

  2. COPY package.json bun.lock ./ RUN bun install --frozen-lockfile --production

    Installs exactly what bun.lock pins and skips devDependencies. --frozen-lockfile fails the build if the lockfile doesn't match package.json.

  3. ENV NODE_ENV=production

    Many libraries check NODE_ENV to disable development features, and Bun sets the same variable convention.

  4. COPY --from=deps --chown=bun:bun /app/node_modules ./node_modules

    Copies the installed dependencies, owned by the non-root bun user that ships with the image.

  5. COPY --chown=bun:bun . .

    Adds your source files. Bun runs TypeScript natively, so there is no build stage or dist folder to manage.

  6. USER bun EXPOSE 3000 CMD ["bun", "run", "index.ts"]

    Starts the server as the unprivileged user. Bun is the main process, so it receives stop signals directly.

Build and run

terminal
docker build -t bun-app .
docker run --rm -p 3000:3000 bun-app

Build from the project root and open http://localhost:3000. Configuration goes in environment variables at runtime, which Bun exposes through process.env and Bun.env.

Other Bun Dockerfile variants

The Alpine image keeps Bun containers small. A Debian-based oven/bun image suits dependencies that need glibc, and a compiled single-file executable variant can drop the runtime entirely. Verified variants appear in the table below.

VariantRuntime imageImage sizeCI
Bun runtime on Alpineoven/bun:1-alpine127.4 MBPassing

Troubleshooting

error: lockfile had changes, but lockfile is frozen
package.json changed without updating bun.lock. Run bun install locally, commit the new lockfile, and rebuild.
COPY failed: file not found in build context: bun.lockb
Older projects use the binary bun.lockb. Upgrade with bun install --save-text-lockfile, or change the COPY line to the file your project has.
A package is missing at runtime
It's listed under devDependencies, so --production skipped it. Move packages your code imports while running into dependencies.

FAQ

Do I need a build step for TypeScript with Bun?
No. Bun transpiles TypeScript on the fly when it loads your files. A build step only helps if you want a bundled or compiled single-file output.
Why pin oven/bun:1 instead of a full version?
The 1 tag gets fixes automatically, and the weekly CI rebuild catches breaking changes. Pin an exact version for fully reproducible production images.
Can I use this with Hono or Elysia?
Yes. Both run on Bun.serve. Keep the entry file as index.ts, or point the CMD line at your framework's entry file.