Skip to main content
This guide assumes you already have Docker Desktop installed.
Docker is a platform for packaging and running an application as a lightweight, portable container that encapsulates all the necessary dependencies.
To containerize our application, we define a Dockerfile. This file contains a list of instructions to initialize the container, copy our local project files into it, install dependencies, and starts the application.
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" ]

Now that you have your docker image, let’s look at .dockerignore which has the same syntax as .gitignore, here you need to specify the files/directories that must not go in any stage of the docker build. An example for a ignore file is
.dockerignore
node_modules
Dockerfile*
docker-compose*
.dockerignore
.git
.gitignore
README.md
LICENSE
.vscode
Makefile
helm-charts
.env
.editorconfig
.idea
coverage*

We’ll now use docker build to convert this Dockerfile into a Docker image, a self-contained template containing all the dependencies and configuration required to run the application. The -t flag lets us specify a name for the image, and --pull tells Docker to automatically download the latest version of the base image (oven/bun). The initial build will take longer, as Docker will download all the base images and dependencies.
terminal
docker build --pull -t bun-hello-world .
[+] Building 0.9s (21/21) FINISHED
 => [internal] load build definition from Dockerfile                                                                                     0.0s
 => => transferring dockerfile: 37B                                                                                                      0.0s
 => [internal] load .dockerignore                                                                                                        0.0s
 => => transferring context: 35B                                                                                                         0.0s
 => [internal] load metadata for docker.io/oven/bun:1                                                                                    0.8s
 => [auth] oven/bun:pull token for registry-1.docker.io                                                                                  0.0s
 => [base 1/2] FROM docker.io/oven/bun:1@sha256:373265748d3cd3624cb3f3ee6004f45b1fc3edbd07a622aeeec17566d2756997                         0.0s
 => [internal] load build context                                                                                                        0.0s
 => => transferring context: 155B                                                                                                        0.0s
 # ...lots of commands...
 => exporting to image                                                                                                                   0.0s
 => => exporting layers                                                                                                                  0.0s
 => => writing image sha256:360663f7fdcd6f11e8e94761d5592e2e4dfc8d167f034f15cd5a863d5dc093c4                                             0.0s
 => => naming to docker.io/library/bun-hello-world                                                                                       0.0s

We’ve built a new Docker image. Now let’s use that image to spin up an actual, running container. We’ll use docker run to start a new container using the bun-hello-world image. It will be run in detached mode (-d) and we’ll map the container’s port 3000 to our local machine’s port 3000 (-p 3000:3000). The run command prints a string representing the container ID.
terminal
docker run -d -p 3000:3000 bun-hello-world
7f03e212a15ede8644379bce11a13589f563d3909a9640446c5bbefce993678d

The container is now running in the background. Visit localhost:3000. You should see a Hello, World! message.
To stop the container, we’ll use docker stop <container-id>.
terminal
docker stop 7f03e212a15ede8644379bce11a13589f563d3909a9640446c5bbefce993678d

If you can’t find the container ID, you can use docker ps to list all running containers.
terminal
docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
7f03e212a15e        bun-hello-world     "bun run index.ts"       2 minutes ago       Up 2 minutes        0.0.0.0:3000->3000/tcp   flamboyant_cerf

That’s it! Refer to the Docker documentation for more advanced usage.
I