How To: Build and Run a Next.js App with Docker

Have a Next.js project that you’d like to make even more portable? This guide will show you how to get your project running with Docker and Bun within minutes.

/images/blog/cover-images/nextjs-docker.png
Docker and Docker Compose for a Next.js and Bun project

by on

Next.js is an increasingly popular React framework for building visually stunning, high-performing, and easy-to-use webapps. You can take your Next.js app one step further by containerizing it with Docker. Once you have it Dockerized, you can more easily share it across machines, cloud host it, and store config (among many, many other things).

TLDR: Check out this repo for the resulting Docker and Docker Compose files for running a Next.js app with Bun.

Using Bun as your JavaScript runtime

In this tutorial, we’re going to use Bun to initialize, bundle, and run our Next.js application. Bun is this decade’s answer to Node.js — it’s substantially lighter and faster, thanks to its JavaScriptCore-based architecture (less memory usage and quicker startup at the cost of optimized execution speed).

With Next.js, you can (and perhaps should) develop in TypeScript. Node.js doesn’t natively support TypeScript, rather it transpiles it to JavaScript using ts-node. Since Bun has TypeScript support right out of the box, we won’t need any extra config and we’ll also save on time/compute.

Initializing your Next.js app with Bun

You can skip this section if you already have a Next.js app up and running.

If you haven’t already, check out the docs and install Bun.

To generate a working Next.js base app that builds and runs with Bun, you can use this command:

bun create next-app

You’ll then be presented with a series of configuration options. For our purposes, we can stick with the defaults.

And from there, you can run your app and access its dev server with:

bun --bun run dev

Go to localhost:1313 to check it out.

Writing the Dockerfile for a Next.js / Bun app

Our project’s Dockerfile will be incredibly simple and standard. We’re working with a single-service, classic Next.js app, so we’ll only need to write one Dockerfile with limited config.

Start by creating a Dockerfile in your project’s root.

Step 1: Setting the base image

Bun offers a lightweight Docker image that we’ll build our app on top of. It contains the Bun runtime bundled with necessary dependencies.

FROM oven/bun

Alternatively, you might want to use a lighter-weight Docker image for this. Consider:

FROM oven/bun:alpine

Step 2: Setting the working directory

Here, we’ll define the working directory inside of the container. That means we can call it whatever we want at this point — it doesn’t need to match the name of the directory on our local machine. But for consistency, I like to set it as /app:

WORKDIR /app

Step 3: Bringing over our dependencies

Right now, our container’s app directory is empty. Since Dockerfiles cache in layers, we want to copy over the dependencies first (we’re less likely to edit these files than the rest of our app). Docker’s caching ensures we don’t have to sit through the lengthy copy/install commands every time we edit app files.

COPY package*.json ./

Step 4: Installing dependencies

This command just installs the dependencies defined in package.json to your working directory:

RUN bun install

Step 5: Copy the rest of your app

Now that we have dependencies set up within the container, we can copy over the remaining app files from our local machine (minus those defined in the .dockerignore):

COPY . .

Step 6: Running the start command

Lastly, we’ll run this Bun command to start the dev server:

RUN bun --bun run dev

The resulting Dockerfile

And that’s it! Our Next.js project is fully Dockerized now.

FROM oven/bun

WORKDIR /app

# Copy package.json + package-lock.json
COPY package*.json ./

# Install dependencies
RUN bun install

# Copy the rest of the application
COPY . .

# Start the dev server
CMD ["bun", "--bun", "run", "dev"]

Writing the Docker Compose file for a Next.js / Bun app

Now that we have the app containerized with a Dockerfile, we can write a simple Compose file to make it extra quick and easy to run. Once we do this, we can run docker compose up to get it started instead of copy/pasting complex docker build and docker run commands.

Create a file called compose.yml in your project’s root directory.

Step 1: Defining the Next.js service

This project only has one Dockerfile, so we’ll only need one service. Its name doesn’t exactly matter, but it’s best practice to choose a name relevant to the project:

version: '3.8'

services:
  next:

Step 2: Setting build context

The build context defines the path(s) that the Docker build has access to. Setting the context to . will include our entire repo in the build.

 build:
   context: .

Step 3: Setting ports

The ports option maps the host port to the container port. This means you’ll access your app via localhost:3000 and any requests made will be forwarded to the container at port 3000.

ports:
  - '3000:3000'

Step 4: Adding volumes

The volumes option shares data between the host machine and the containerized app. This will persist any data created while the container is running. So next time you launch your app, you’ll see your app’s state just as you left it.

The syntax for a volume will be the directory’s local path in your project, followed by a colon, followed by where you’re mounting the directory within the container.

volumes:
  - './public:/app/public'
  - './src:/app/src'

The resulting Compose file

There we have it — a complete Docker Compose file. Our project is ready to be spun up now…

version: '3.8'

services:
  next:
    build:
      context: .
    ports:
      - '3000:3000'
    volumes:
      - './public:/app/public'
      - './src:/app/src'

Running the Next.js app in a Docker container

Now that we have both a Dockerfile and a Docker Compose, we can spin this project up with a single command. Launch the Docker daemon and from the app’s root, you can run docker compose up to build and run the project as defined. You’ll be able to access it on localhost:3000.

When you’re done, you can clean up and remove your containers with docker compose down.

What’s Next?

One of the greatest things about Docker containers is that they live throughout every stage of the dev cycle. If you’re developing locally with Docker and Compose, you should be able to then access your app just as easily in a CDE and then an ephemeral environment, with limited config modifications. Consider kicking off a free 30-day trial of Shipyard to bring production-like testing and orchestration to your Next.js app.

Try Shipyard today

Get isolated, full-stack ephemeral environments on every PR.

What is Shipyard?

Shipyard is the Ephemeral Environment Self-Service Platform.

Automated review environments on every pull request for Developers, Product, and QA teams.

Stay connected

Latest Articles

Shipyard Newsletter
Stay in the (inner) loop

Hear about the latest and greatest in cloud native, container orchestration, DevOps, and more when you sign up for our monthly newsletter.