Hi, everyone!!
This is my first blog post and I'm so happy!!
The Project
In this post, I would like to talk about an issue I encountered while writing a dockerfile for a Next.js/Prisma application and the solution that I found.
I'm developing a project as a volunteer for a non-profit organization (Istituto Buddista Italiano Soka Gakkai) and we'll hit production soon.
The repository is accessible at this link
The project is a website for consulting antique Buddhist scriptures of the Nichiren Daishonin school and conducting advanced research on them.
Because the organization already has a well-structured infrastructure with databases, ElasticSearch nodes, and other WordPress websites, we've decided to use docker instead of going for the classic Vercel deployment and have everything pretty much in the same location or close to each other. If something changes and we have to migrate our production system, we'll be safe doing so with containers.
The Issue
Thanks to the create-t3-app documentation I was able to set up the Dockerfile pretty quickly, but I've encountered one issue with this setup (the documentation might change after I post this).
In our case, the Next.js application is fetching data from the database at build time (Server Side Generation) to generate pages and cache them. To do that, we need the database URL available during the docker build.
This database URL contains password credentials and cannot be passed to the docker build as an ARG
. ARG
s can be easily inspected by everyone if the docker image is publicly available, and an attacker could find the database URL, username, and password using just docker history
(See documentation about this issue here).
The Solution
Sensitive information has to be handled through docker secrets.
So, the best solution that i could find is this:
RUN --mount=type=secret,id=ENV_WITH_SECRETS,required \
source /run/secrets/ENV_WITH_SECRETS && \
DATABASE_URL=$DATABASE_URL yarn build
With this command, docker mounts the environment file with secrets just for this line. The variable is loaded from that file and can be accessed in the code using process.env
by Next.js and Prisma. Prisma by default use the .env
file to load the database url, but in our case we have to modify this behavior with the follow line:
const prisma = new PrismaClient({
datasources: {
db: {
url: process.env.DATABASE_URL,
},
},
})
With my current version of Prisma, the DATABASE_URL
variable even if it's in the secret file, gets removed just at the start of yarn build
from the process.env
, so a workaround is to run DATABASE_URL=$DATABASE_URL yarn build
to enforce the variable in prisma.
The build command looks something like this:
docker build . --secret id=ENV_WITH_SECRETS,src=./.env -t nextjs-prisma-application
The id (ENV_WITH_SECRETS
in this example) is absolutely arbitrary.
The secret
file contain the database url with credentials that we need at build time.
It is even easier to use secrets with Docker Compose (documentation).
The syntax depends on your docker compose version. This is the syntax that i used for the 2.18.1
version:
services:
app:
platform: "linux/amd64"
build:
context: .
dockerfile: Dockerfile
secrets:
- ENV_WITH_SECRETS
working_dir: /app
ports:
- "3000:3000"
image: nextjs-app
env_file:
- .env
secrets:
ENV_WITH_SECRETS:
file: .env
I bet you don't build every docker image manually yourself, though, do you?
Here is how docker/build-push-action
handles secrets:
- name: Build and push
id: docker_build
uses: docker/build-push-action@v4
with:
push: true
tags: tag
secrets: |
"ENV_WITH_SECRETS=${{ secrets.ENV_WITH_SECRETS }}"
The use of "
is essential here to assign a multi-line secret file in this way (documentation).
In this case, ENV_WITH_SECRETS
is the docker secret id used previously.
At this point, on Github repo settings you can add a secret with the same ENV_WITH_SECRETS
name and copy-paste the .env
file containing multiple secrets.
For example
ELASTIC_SEARCH_PASSWORD=*****
DATABASE_URL=""mysql://username:password@host:post/db""
Note: on github you have to escape double quotes character ("
) with another double quotes character.
To be able to access the database at runtime, (for revalidating the pages on demand or automatically), we can add an environment variable called DATABASE_URL
just before the CMD
.
ENV DATABASE_URL ''
CMD ["node", "server.js"]
Environment variables are only available at runtime and not in the build step, which makes it safe to use them for sensitive information.
Conclusion
It was so fun to dig into this issue, learn more about docker secrets, and find a solution.
I also posted a pull request against create-t3-app repo and they helped me find a suitable solution for many more environments.
It was a great opportunity to write a post!!
Thanks!
I'm looking forward to your suggestions and comments!!
Top comments (0)