DEV Community

Léo Pradel
Léo Pradel

Posted on • Originally published at leopradel.com

Dockerize a nitro application

Nitro is a server toolkit/framework that allows you to create web servers with ease and deploy them wherever you prefer.

In this tutorial, we will create an optimised Docker image so you can deploy your app on any VPS or provider.

Create a new simple nitro server

Let's start by creating a new Nitro server. We will use a really simple "hello world" app for this tutorial. To create and install the template, run the following command in your terminal:

yarn dlx giget@latest nitro nitro-app --install
cd nitro-app
Enter fullscreen mode Exit fullscreen mode

Let's try the application to make sure that everything is correct. To run the application in development mode, run this command:

yarn run dev
Enter fullscreen mode Exit fullscreen mode

In your terminal, you can see from the logs that the application is running properly.

> dev
> nitro dev

  ➜ Local:    http://localhost:3000/
  ➜ Network:  use --host to expose

✔ Nitro Server built in 181 ms
Enter fullscreen mode Exit fullscreen mode

Now, open your browser and go to the URL http://localhost:3000, you should see the following message Start by editing server/routes/index.ts..

Let's edit server/routes/index.ts and change it to the following:

export default eventHandler((event) => {
  return { message: 'Hello world!' };
});
Enter fullscreen mode Exit fullscreen mode

Save the file and refresh your page and you should now see some JSON returned by the nitro server.

{
  "message": "Hello world!"
}
Enter fullscreen mode Exit fullscreen mode

Finally, let's close the development server by running Ctrl+C in your terminal.

Dockerize the nitro app

Our application is now ready, let's create the docker config. First, we create a new .dockerignore file that will tell Docker to not copy these local files and folders when building the image.

# .dockerignore

node_modules
# Files generated by nitro
.nitro
.output
Enter fullscreen mode Exit fullscreen mode

Create the optimized Dockerfile

Next, let's create a new Dockerfile file that will contain the instructions for building the Docker image. We will use a multi-stage build to create an optimized image.

The fist step is to install the dependencies, this is done as a separate step to take advantage of Docker's caching mechanism, so that we don't have to reinstall the dependencies every time we rebuild the image.

# Dockerfile
FROM node:20-alpine AS base

# Install dependencies
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
Enter fullscreen mode Exit fullscreen mode

Then, we build the nitro app in a separate step. This will generate the optimized output in the .output folder.

# Dockerfile

# Install dependencies
# ... (same as before)

# Build the nitro app
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN yarn run build
Enter fullscreen mode Exit fullscreen mode

Finally, we create an optimized runner image that will run the nitro app. We copy the optimized output from the builder image and set the user to nitro to run the app as a non-root user. If you are familiar with Docker, you will notice that we don't copy the node_modules folder to the runner image, this is because nitro already includes all the dependencies in the optimized .output folder.

# Dockerfile
FROM node:20-alpine AS base

# Install dependencies
# ... (same as before)

# Build the nitro app
# ... (same as before)

# Create an optimised runner image
FROM base AS runner
WORKDIR /app
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nitro
COPY --from=builder /app/.output ./.output
USER nitro
EXPOSE 3000
ENV PORT 3000
CMD ["node", ".output/server/index.mjs"]
Enter fullscreen mode Exit fullscreen mode

Our final Dockerfile should look like this:

FROM node:20-alpine AS base

# Install dependencies
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile

# Build the nitro app
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN yarn run build

# Create an optimised runner image
FROM base AS runner
WORKDIR /app
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nitro
COPY --from=builder /app/.output ./.output
USER nitro
EXPOSE 3000
ENV PORT 3000
CMD ["node", ".output/server/index.mjs"]
Enter fullscreen mode Exit fullscreen mode

Build the Docker image

Let's build our application Docker image, the -t flag is used to tag it with the name we want. Run the following command:

docker build . -t nitro-app
Enter fullscreen mode Exit fullscreen mode

Run the Docker container

Finally, we can run the docker container to test that the app is working properly. Run the following command:

docker run -p 3000:3000 nitro-app
Enter fullscreen mode Exit fullscreen mode

In your terminal, you can see in the Docker logs that the application is running on port 3000.

Listening on http://[::]:3000
Enter fullscreen mode Exit fullscreen mode

Open your browser at http://localhost:3000 and see the same "Hello world!" JSON as before.

Conclusion

Dockerizing a nitro server is very similar to other Node.js applications, except that the optimised build step will generate a smaller Docker image. You can now deploy your Docker image whenever you want (VPS, fly.io, Render etc..).

Top comments (0)