← Back to Blog

Dockerizando NestJS + Prisma 7 + Yarn

Aplicação Utilizada

A aplicação utilizada para a demonstração será esta — o blog que você está lendo neste momento. A versão do Prisma ORM utilizada é a 6.13, porém estamos utilizando o generator prisma-client, que se tornará o padrão na versão 7.0 e exige a opção output explicitamente, diferente do prisma-client-js, que usava o node_modules como local padrão. Para futura referência, o commit do momento em que escrevo é o cf0483e.

Assumindo que você tenha corretamente instalado suas dependências com separação entre dev e prod, o projeto se beneficiará de uma build em duas etapas — uma para o build e outra, final, para a execução. Vejamos o Dockerfile.

Dockerfile

Antes de continuar, é importante notar que no projeto há um .dockerignore que impede que diretórios indesejados ou desnecessários sejam copiados para a imagem:

dist/  # arquivos transpilados localmente
node_modules/  # dependências locais previamente instaladas
...

Finalmente, eis o Dockerfile:

# Build stage
FROM node:24-alpine AS builder

WORKDIR /app

COPY package.json yarn.lock ./
COPY prisma ./prisma/

RUN yarn install --frozen-lockfile

COPY . .

RUN npx prisma generate && yarn build

# Production stage
FROM node:24-alpine AS production

WORKDIR /app

COPY package.json yarn.lock ./
COPY prisma ./prisma

RUN yarn install --frozen-lockfile --production && yarn cache clean

COPY views ./views
COPY public ./public

COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules/prisma/libquery_engine-linux-musl-openssl-3.0.x.so.node ./dist/src/generated/prisma/client

EXPOSE 3000

CMD ["sh", "-c", "npx prisma migrate deploy && yarn start:prod"]
Vamos entender o que está sendo feito:

Multi-stage Build

O Docker nos permite utilizar múltiplos containers, de imagens iguais ou não, para construir uma imagem final. O nome disto é multi-stage build. Esta prática é útil quando o projeto possui dependências que só são necessárias durante o desenvolvimento da aplicação; no caso do Typescript, podemos apontar o próprio transpilador da linguagem como tal. Outras dependências de desenvolvimento incluem, usualmente, linters e frameworks de testes.

Utilizando o multi-stage build, podemos usar uma imagem (linhas 2-13) para transpilar o código, onde instalaremos todas as dependências disponíveis, e outra (linhas 16-33) para executar a aplicação, onde instalaremos apenas as dependências necessárias para execução.

Build stage

Eis o que é feito no estágio de build, passo a passo:

  1. Utilizamos a imagem node-alpine como base por seu tamanho pequeno;
  2. É definido o diretório de operação: /app;
  3. Arquivos necessários para a instalação de dependências e geração do cliente prisma são copiados da sua máquina para o container: package.json e prisma/;
  4. Dependências são instaladas com o yarn;
  5. O resto dos arquivos são copiados para o container (todos, exceto os definidos no .dockerignore);
  6. O cliente prisma é gerado e é realizado o build da aplicação.

Production stage

Agora, estaremos trabalhando com um novo container. O container de build ainda nos é acessível: para copiar arquivos do container de build para o novo, podemos utilizar COPY --FROM=builder <src> <dest> — note que builder é o nome que demos para nosso container de build.

Vamos observar o que acontece no estágio de produção. Note que os primeiros passos serão similares:

  1. Também utilizamos a imagem node-alpine como base;
  2. É definido o diretório de operação: /app;
  3. Arquivos necessários para a instalação de dependências e geração do cliente prisma são copiados da sua máquina para o container: package.json e prisma/;
  4. Agora, ao instalarmos as dependências com o yarn, utilizaremos a flag --prod para excluir dependências de desenvolvimento. Após isso, limparemos o cache do yarn com yarn cache clean;
  5. Para a execução do nosso app, os arquivos contidos nos diretórios views/ e public/ são necessários, então os copiamos de nossa máquina para o container final.
  6. Trazemos os arquivos que foram transpilados do container de build para o container de prod.
  7. O último arquivo que trazemos do builder é necessário para que o prisma seja executado em ambientes como o Alpine, que usam musl em vez da mais amplamente utilizada glibc.
  8. Por fim, expomos a porta em que nossa aplicação está rodando e definimos o comando de entrada na imagem que será gerada.

Construindo a Imagem

Podemos construir nossa imagem com o comando docker build . -t seuuser/blog:latest. O resultado é uma imagem final com 485MB — cerca de 320MB a mais do que imagem base node-alpine.