7 min read

Otimizando o DockerFile: Dicas Práticas para Melhorar a Performance

Table of Contents

Otimizando seu Docker: Guia Prático com Node.js e Postgres

Se você está começando no mundo do Docker, provavelmente já se deparou com imagens que ficam gigantescas ou builds que demoram uma eternidade. A boa notícia é que, com algumas técnicas simples, podemos melhorar drasticamente o tamanho e a performance das nossas imagens.

Neste post, vamos criar um projeto do zero: uma aplicação básica em Express.js (Node.js) que se conecta a um banco de dados PostgreSQL. Faremos tudo isso rodar com Docker e, o mais importante, vamos aplicar otimizações essenciais, como imagens Alpine e multi-stage builds.

Vamos lá?

Pré-requisitos

Para seguir este tutorial, você só precisa ter o Docker e o Docker Compose instalados na sua máquina.

Passo 1: Criando a Aplicação Express Básica

Primeiro, vamos criar uma estrutura de pastas simples para nosso projeto.

mkdir meu-projeto-docker
cd meu-projeto-docker

Dentro dessa pasta, crie dois arquivos:

1. faça o npm init -y - Para inicializar um novo projeto Node.js e criar o package.json.

npm init -y
npm install express

crie um arquivo chamado app.js, docker-compose.yaml e outro chamado Dockerfile.

2. app.js - Nosso servidor web super simples.

const express = require('express');
const app = express();
const PORT = 3000;

app.get('/', (req, res) => {
  res.send('Olá, mundo!');
});

app.listen(PORT, () => {
  console.log(`Servidor rodando na porta ${PORT}`);
});

E é isso! Temos um app que simplesmente responde “Olá, mundo!” na rota principal.

Passo 2: O Dockerfile sem otimização

Antes de otimizar, vamos ver como um Dockerfile comum e não otimizado se parece. Isso nos dará uma base de comparação.


# 1. Usa uma imagem grande do Node.js
FROM node:22

# 2. Define o diretório de trabalho
WORKDIR /app

# 3. Copia TUDO para dentro do container
# Se qualquer arquivo mudar, o cache é invalidado aqui
COPY . .

# 4. Instala TODAS as dependências
RUN npm install

# 5. Expõe a porta e inicia o app
EXPOSE 3000
CMD ["node", "app.js"]

para fazer a build basta executar o comando:

docker build -t meu_app_express .

Problemas desta abordagem:

  • Tamanho da Imagem: A imagem node:22 é baseada no Debian e inclui muitas ferramentas que não precisamos para rodar nossa aplicação, resultando em uma imagem final enorme (mais de 900MB!).
  • Cache Ineficiente: A linha COPY . . copia todos os arquivos do projeto de uma vez. Se você alterar apenas um caractere no seu app.js, o Docker invalidará o cache desta camada e de todas as subsequentes, forçando o npm install a rodar novamente, mesmo que as dependências não tenham mudado.

no total a imagem ficou com

meuapp           latest      7350c614c371   3 minutes ago   1.13GB

Passo 3: O Dockerfile Otimizado

Agora, vamos criar o nosso Dockerfile de verdade, usando duas técnicas poderosas:

  1. Imagens Alpine: São versões super enxutas de sistemas operacionais. A node:22-alpine é muito menor que a node:22.
  2. Multi-stage Builds: Permite que usemos um ambiente para “construir” nossa aplicação (instalando dependências) e outro ambiente, limpo e enxuto, para “rodar” a aplicação. Apenas os artefatos necessários são copiados para o estágio final.

Crie o arquivo Dockerfile na raiz do seu projeto com o seguinte conteúdo:

# Dockerfile (Otimizado)

# ---- Estágio 1: Build ----
# Usamos a imagem alpine do Node como um "builder" (construtor)
FROM node:22-alpine AS builder

# Define o diretório de trabalho
WORKDIR /app

# Copia APENAS o package.json e package-lock.json (se existir)
# Isso aproveita o cache do Docker. O npm install só rodará de novo se estes arquivos mudarem.
COPY package*.json ./

# Instala apenas as dependências de produção
RUN npm install --production

# ---- Estágio 2: Produção ----
# Começamos de uma nova imagem limpa e leve
FROM node:22-alpine

WORKDIR /app

# Copia as dependências já instaladas do estágio "builder"
COPY --from=builder /app/node_modules ./node_modules

# Copia o código da nossa aplicação
COPY app.js ./

# Expõe a porta e define o comando para rodar o app
EXPOSE 3000
CMD ["node", "app.js"]

O que ganhamos com isso?

  • Imagens Menores: A imagem final terá um tamanho drasticamente reduzido (geralmente em torno de 150MB), pois é baseada na alpine e contém apenas o necessário para rodar o app.
  • Builds mais Rápidos: Como copiamos o package.json primeiro, a demorada etapa npm install só será executada novamente se as dependências mudarem, não a cada alteração no código.

com essas otimzaçoes a imagem final ficou com:

meuapp           latest      3c8f1b2d4e5a   2 minutes ago   162MB

Passo 4: Orquestrando Tudo com o Docker Compose

Agora precisamos de um banco de dados PostgreSQL e queremos que ele “converse” com nossa aplicação. O Docker Compose é perfeito para gerenciar múltiplos contêineres.

Crie o arquivo docker-compose.yml na raiz do projeto:

# docker-compose.yml

services:
  # Nosso serviço da aplicação Express
  app:
    build: . # Constrói a imagem usando o Dockerfile na pasta atual
    container_name: meu_app_express
    ports:
      - "3000:3000" # Mapeia a porta 3000 do host para a 3000 do contêiner
    depends_on:
      - db # Diz ao Docker para iniciar o serviço 'db' antes do 'app'

  # Nosso serviço do banco de dados PostgreSQL
  db:
    image: postgres:15-alpine # Usando a imagem alpine do Postgres também!
    container_name: meu_banco_postgres
    environment:
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: mysecretpassword
      POSTGRES_DB: mydatabase
    ports:
      - "5432:5432" # Mapeia a porta do Postgres (opcional, bom para debug)
    volumes:
      - postgres_data:/var/lib/postgresql/data # Garante que os dados persistam

# Define um volume nomeado para persistir os dados do banco
volumes:
  postgres_data:

O que este arquivo faz?

  • services: Define os contêineres que farão parte da nossa aplicação.
    • app: Nosso app Node.js. A instrução build: . diz ao Compose para construir a imagem a partir do nosso Dockerfile otimizado.
    • db: Nosso banco de dados. Usamos uma imagem pronta postgres:15-alpine para manter a otimização.
  • environment: Configura as variáveis de ambiente para criar o usuário e o banco de dados no Postgres na primeira vez que ele iniciar.
  • volumes: Esta é uma parte crucial! A linha postgres_data:/var/lib/postgresql/data cria um “volume”. Isso significa que os dados do seu banco serão salvos fora do contêiner, em uma área gerenciada pelo Docker. Se você parar e remover o contêiner, seus dados não serão perdidos!

Passo 5: Rodando o Projeto

Sua estrutura de arquivos final deve ser:

meu-projeto-docker/
├── app.js
├── Dockerfile
├── docker-compose.yml
└── package.json

Agora, no seu terminal, dentro da pasta meu-projeto-docker, execute o comando:

docker-compose up --build
  • up: Inicia os contêineres definidos no docker-compose.yml.
  • --build: Força o Docker a construir a imagem do nosso app antes de iniciá-la.

Você verá os logs de ambos os contêineres no seu terminal. Espere até ver a mensagem Servidor rodando na porta 3000.

Teste sua aplicação! Abra seu navegador e acesse http://localhost:3000. Você deverá ver a mensagem:

Olá, mundo! Meu app Express otimizado está no ar! 🚀

Para parar e remover todos os contêineres, redes e volumes criados, pressione Ctrl + C no terminal e depois execute:

docker-compose down

Conclusão

Parabéns! Você não apenas criou uma aplicação com Node.js e Postgres rodando em Docker, mas também aprendeu duas das otimizações mais importantes para o dia a dia:

  1. Usar imagens -alpine para reduzir drasticamente o tamanho final.
  2. Implementar multi-stage builds para criar imagens menores e acelerar o processo de build.

Essas práticas simples tornam seus projetos mais eficientes, fáceis de gerenciar e mais rápidos de implantar. A partir daqui, você pode evoluir o projeto para realmente conectar o Express ao Postgres, mas a base otimizada já está pronta!