Secure your Dockerfile for SSG with NextJS and Prisma
Tags: prisma, nextjs, docker, security
Published: 2023-05-02 13:17:53

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](https://github.com/upalatucci/biblioteca-nextjs)

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](https://create.t3.gg/en/deployment/docker) 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](https://docs.docker.com/engine/reference/builder/#arg)).

## The Solution

Sensitive information has to be handled through [docker secrets](https://docs.docker.com/engine/reference/builder/#run---mounttypesecret).

So, the best solution that i could find is this:

```Dockerfile
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:

```javascript
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:

```bash
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](https://docs.docker.com/compose/compose-file/compose-file-v3/#secrets)). 

The syntax depends on your docker compose version. This is the syntax that i used for the `2.18.1` version:

```yaml
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: 

```yaml
- 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](https://docs.docker.com/build/ci/github-actions/secrets/)).

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 

```bash
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`.

```Dockerfile
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](https://github.com/t3-oss/create-t3-app/pull/1368) 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!!