Skip to main content
Docker is a platform for packaging and running an application as a lightweight, portable container that encapsulates all the necessary dependencies.
This guide assumes you already have Docker Desktop installed.

Official images

Bun publishes official images to Docker Hub under oven/bun. Several variants are available:
TagDescription
oven/bun:latestLatest stable release, based on Alpine Linux
oven/bun:1Latest Bun 1.x release
oven/bun:slimMinimal Alpine-based image with no extras
oven/bun:debianDebian-based image for broader package compatibility
oven/bun:canaryLatest 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.
Dockerfile
# 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.
.dockerignore
node_modules
Dockerfile*
docker-compose*
.dockerignore
.git
.gitignore
README.md
LICENSE
.vscode
.env
coverage*

Build and run

1

Build the image

The -t flag names the image and --pull ensures Docker downloads the latest base image.
docker build --pull -t bun-app .
2

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.
3

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:
docker-compose.yml
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:
docker compose up -d

Health checks

Add a health check endpoint to your Bun server so Docker can monitor the container:
index.ts
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.

Build docs developers (and LLMs) love