Build the Docker image of a Node.js project using Prisma
Prisma is a world-class ORM in the Node.js ecosystem, providing a seamless way to design your database schema, connect your Node.js application to your database, and perform SQL queries.
With a great developer experience from the build locally until the deployment in production. Docker is one of the favorite ways to distribute and deploy applications; when packaging a Node.js application using Prisma, it can be challenging to make it work.
This tutorial shows you how to build the Docker image of a Node.js application that uses Prisma.
Prerequisites
To follow this tutorial, make sure you have the following tools installed on your computer.
- Node.js 18+ - Download's link
- NPM or Yarn - I will use Yarn
- Docker - Download's link
Set up the project
As an example of an existing Node.js project, let's use the one we built during the tutorial about creating a Node.js REST API using Prisma and PlanetScale.
The final source code can be found on the GitHub repository. Let's clone the project locally using the Git sparse checkout.
git clone --no-checkout https://github.com/tericcabrel/blog-tutorials.git
cd blog-tutorials
git sparse-checkout init --cone
git sparse-checkout set node-prisma-rest-api
git checkout @
The project is now available locally; let's make it work by running the following commands:
cd node-prisma-rest-api
docker run -d --rm -e MYSQL_ROOT_PASSWORD=secret -e MYSQL_DATABASE=blogdb --name prisma_db -p 3307:3306 mysql:8.0.34
cp .env.example .env
# Set the DATABASE_URL to "mysql://root:secret@localhost:3307/blogdb"
yarn install
yarn prisma migrate dev
yarn ts-node prisma/seed.ts
yarn start
The above commands do the following tasks:
- Start a Docker container from the Docker image of MySQL.
- Set the database URL in the environment variables file.
- Install the Node.js dependencies.
- Execute the database migrations using Prisma.
- Seed the MySQL database with some users
- Start the application.
The application starts at http://localhost:4500; let's try the application by sending a request to create a post and another one to retrieve all posts:
curl --location 'http://localhost:4500/posts' \
--header 'Content-Type: application/json' \
--data '{
"authorId": 1,
"title": "Build a rest api with prism ",
"content": "This post will show you how to build an awesome api",
"isFeatured": false,
"status": "draft"
}'
curl --location 'http://localhost:4500/posts'
You get the following output:
Build the Docker image
The API works well; let's package it as a Docker image to distribute it efficiently. In the project root directory, create a Dockerfile and add the content below:
FROM node:20-bullseye as builder
RUN mkdir app
WORKDIR /app
COPY . .
RUN yarn install --frozen-lockfile
RUN yarn prisma generate
RUN yarn tsc
FROM node:20-alpine AS runner
ENV NODE_ENV=production
WORKDIR /app
COPY --chown=node:node --from=builder /app/package.json .
COPY --chown=node:node --from=builder /app/build .
RUN yarn install --production && yarn cache clean --all
COPY --chown=node:node --from=builder /app/node_modules/.prisma/client ./node_modules/.prisma/client
EXPOSE 4500
CMD ["node", "src/index.js"]
The Docker build has two stages:
- Builder: It runs a Debian image with Node.js 20 installed. Inside this image, the following tasks are performed:
- Install the Node.js dependencies for the project.
- Generate the TypeScript types for entities from the Prisma schema.
- Transpile the project to JavaScript so Node.js can run it natively.
- Runner: It runs an Alpine image with Node.js 20, which is light compared to the Debian image. Inside this image, the following tasks are performed:
- Set the environment to production
- From the Builder stage, copy the package.json and the build folder generated after transpiling the TypeScript code to JavasScript.
- Install dependencies only for production and clean the Yarn cache.
- Expose the port 4500 and define the command to start the application.
Run the following command to build the Docker image:
docker build -t node-prisma:latest .
The Prisma Engines
The Docker image of the Node.js application is ready; let's start a container from the image by running the command below:
docker run -it node-prisma:latest
We get the following output:
This error is related to the Prisma Engine, an essential element of the Prisma ORM.
It is responsible for managing physical connections to the database, receiving instruction from the Prisma client, transforming it to an SQL query, sending it to the database, retrieving the response, and transforming it back to the Prisma client.
The query engine is a different operating system, and it is downloaded when generating the TypeScript types from the Prisma schema. The cause of the error we get is related to this particularity.
In the "builder" stage, we generate the types from the Prisma schema, which runs on Debian bullseye, but the "runner" stage, which is the final stage, runs the application with Alpine Linux.
Check out the list of Prisma Query engines per operating system.
Add the Prisma engines to the Docker image
We must generate the engines for Alpine Linux to fix the Prisma query engine issue. To optimize the build, we will add an intermediary build stage between "builder" and "runner" called "engine-builder".
The build stage "engine-builder" runs on Alpine with Node.js 20 and generates the TypeScript types from the Prisma schema.
FROM node:20-alpine as engine-builder
WORKDIR /app
COPY --chown=node:node --from=builder /app/prisma/schema.prisma ./app/prisma/
RUN npx prisma generate --schema=./app/prisma/schema.prisma
In the build stage "runner," we copy the Prisma client from the "engine-builder" stage instead of the "builder" stage.
COPY --chown=node:node --from=engine-builder /app/node_modules/.prisma/client ./node_modules/.prisma/client
Here is the complete Dockerfile:
FROM node:20-bullseye as builder
RUN mkdir app
WORKDIR /app
COPY . .
RUN yarn install --frozen-lockfile
RUN yarn prisma generate
RUN yarn tsc
FROM node:20-alpine as engine-builder
WORKDIR /app
COPY --chown=node:node --from=builder /app/prisma/schema.prisma ./app/prisma/
RUN npx prisma generate --schema=./app/prisma/schema.prisma
FROM node:20-alpine AS runner
ENV NODE_ENV=production
WORKDIR /app
COPY --chown=node:node --from=builder /app/package.json .
COPY --chown=node:node --from=builder /app/build .
RUN yarn install --production && yarn cache clean --all
COPY --chown=node:node --from=engine-builder /app/node_modules/.prisma/client ./node_modules/.prisma/client
EXPOSE 4500
CMD ["node", "src/index.js"]
Build the Docker image from the Dockerfile and run it:
docker build -t node-prisma:latest .
docker run -it node-prisma:latest
Now we get the following error:
There is still an error, but not related to the Prisma Query engine but the missing environment variable DATABASE_URL
, which is the connection to the database. Run the command with the connection's URL to your remote database.
docker run -it -p 4501:4500 \
-e "DATABASE_URL=mysql://<DB_USER>:<DB_PASSWORD>@<DB_HOST>/<DB_NAME>" \ prisma:latest
Replace <DB_USER>, <DB_PASSWORD>, <DB_HOST> and <DB_NAME> with your database credentials.
You will get an output similar to this:
Publish and deploy the Docker image
The Docker image is ready to be distributed and pulled from anywhere worldwide. You can publish the image on a Docker registry such as Docker Hub, Amazon Elastic Container Registry, Github Registry, etc...
To publish the image on the Docker Hub, run the following commands
docker login --username=<docker_hub_username>
docker tag node-prisma:latest <docker_hub_username>/node-prisma:latest
docker push <docker_hub_username>/node-prisma:latest
Replace the <docker_username> with your Docker hub username.
From a Virtual private server, run the following commands to pull to the Docker image and run it:
docker pull tericcabrel/node-prisma:latest
docker run -d --rm -p 4510:4500 --name node-rest-api \
-e "DATABASE_URL=mysql://<DB_USER>:<DB_PASSWORD>@<DB_HOST>/<DB_NAME>" \ tericcabrel/node-prisma:latest
curl --location 'http://localhost:4510/posts' \
--header 'Content-Type: application/json' \
--data '{
"authorId": 1,
"title": "Build a rest api with prisma",
"content": "This post will show you how to build an awesome api",
"isFeatured": false,
"status": "draft"
}'
curl --location 'http://localhost:4510/posts'
The last two commands send a request to the API to respectively create a post and retrieve the posts list.
You successfully deployed the Docker image of a Node.js application using Prisma. The API is unaccessible to the internet; you must expose it from a domain's name and configure a reverse proxy to route the traffic to the container. I show you how to do that in the tutorial below:
Wrap up
We saw how to build and push the Docker image to the Docker Hub. We pulled it from the Docker Hub from a Virtual Private Server and ran a container of the image.
When building the Docker image of a Node.js application that uses the ORM Prisma, include the suitable Prisma engines for the operating system the applications will run on.
To learn more about the Prisma engine, check out the Prisma official documentation.
You can find the code source on the GitHub repository.
Follow me on Twitter or subscribe to my newsletter to avoid missing the upcoming posts and the tips and tricks I occasionally share.