Docker is a platform for packaging and running an application as a lightweight, portable container that encapsulates all the necessary dependencies.
Official images
Bun publishes official images to Docker Hub under oven/bun. Several variants are available:
| Tag | Description |
|---|
oven/bun:latest | Latest stable release, based on Alpine Linux |
oven/bun:1 | Latest Bun 1.x release |
oven/bun:slim | Minimal Alpine-based image with no extras |
oven/bun:debian | Debian-based image for broader package compatibility |
oven/bun:canary | Latest canary (pre-release) build |
Multi-stage Dockerfile
A multi-stage build separates dependency installation from the final release image, keeping the production image small and fast.
# use the official Bun image
# see all versions at https://hub.docker.com/r/oven/bun/tags
FROM oven/bun:1 AS base
WORKDIR /usr/src/app
# install dependencies into temp directory
# this will cache them and speed up future builds
FROM base AS install
RUN mkdir -p /temp/dev
COPY package.json bun.lock /temp/dev/
RUN cd /temp/dev && bun install --frozen-lockfile
# install with --production (exclude devDependencies)
RUN mkdir -p /temp/prod
COPY package.json bun.lock /temp/prod/
RUN cd /temp/prod && bun install --frozen-lockfile --production
# copy node_modules from temp directory
# then copy all (non-ignored) project files into the image
FROM base AS prerelease
COPY --from=install /temp/dev/node_modules node_modules
COPY . .
# [optional] tests & build
ENV NODE_ENV=production
RUN bun test
RUN bun run build
# copy production dependencies and source code into final image
FROM base AS release
COPY --from=install /temp/prod/node_modules node_modules
COPY --from=prerelease /usr/src/app/index.ts .
COPY --from=prerelease /usr/src/app/package.json .
# run the app
USER bun
EXPOSE 3000/tcp
ENTRYPOINT [ "bun", "run", "index.ts" ]
.dockerignore
Create a .dockerignore file to exclude files from the build context. This speeds up builds and prevents secrets from leaking into the image.
node_modules
Dockerfile*
docker-compose*
.dockerignore
.git
.gitignore
README.md
LICENSE
.vscode
.env
coverage*
Build and run
Build the image
The -t flag names the image and --pull ensures Docker downloads the latest base image.docker build --pull -t bun-app .
Run the container
Run in detached mode (-d) and map port 3000 on the container to port 3000 on your machine.docker run -d -p 3000:3000 bun-app
Visit http://localhost:3000 to confirm the app is running. Stop the container
List running containers and stop by ID.docker ps
docker stop <container-id>
Docker Compose
For local development with multiple services, use Docker Compose:
services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
restart: unless-stopped
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_USER: user
POSTGRES_PASSWORD: password
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
Start the stack with:
Health checks
Add a health check endpoint to your Bun server so Docker can monitor the container:
Bun.serve({
port: 3000,
fetch(req) {
const url = new URL(req.url);
if (url.pathname === "/health") {
return new Response("OK", { status: 200 });
}
return new Response("Hello from Bun!");
},
});
The HEALTHCHECK instruction in the Dockerfile (or the healthcheck key in Compose) will poll this endpoint and mark the container as unhealthy if it fails.
Use oven/bun:debian instead of the default Alpine image when you need to install native packages (e.g., libvips for image processing) that require a glibc-based system.