Como dockerizar uma aplicação (Dockerfile passo a passo)

Para dockerizar uma aplicação, escreva um Dockerfile com a imagem base, as dependências, o código e o comando de início. Depois rode docker build para gerar a imagem e docker run para executá-la.

Atualizado em 5 min de leitura

Objetivos de aprendizado

Depois de ler este artigo, você vai conseguir:

  • Escrever um Dockerfile funcional para uma aplicação Node.js
  • Buildar a imagem e rodar o container com variáveis de ambiente
  • Reduzir o tamanho da imagem com .dockerignore, cache de camadas e multi-stage
Nesta página

"Na minha máquina funciona" costuma ser culpa de diferenças pequenas: versão do Node, uma biblioteca do sistema, uma variável esquecida. Dockerizar uma aplicação resolve isso empacotando tudo o que ela precisa numa imagem, que se comporta igual no seu notebook, no CI e num servidor.

O ponto de partida é um arquivo de texto chamado Dockerfile. O exemplo usa Node.js, mas a lógica vale para qualquer linguagem: escolher uma base, instalar dependências, copiar o código, definir o comando de início.

DockerfileCódigodockerbuildImagemcódigodependênciasimagem basecamadassomente leituradockerrunContainerno notebookContainerno CIContainerno servidor
O build empacota Dockerfile e código numa imagem em camadas; o run inicia essa mesma imagem em qualquer máquina.

O que você precisa antes de começar?#

  • Docker instalado (Docker Desktop no Windows e macOS, ou Docker Engine no Linux). Confira com docker --version.
  • Uma aplicação com um comando de início conhecido. No exemplo, um package.json e um package-lock.json, com o código principal em src/index.js.
  • Um terminal aberto na pasta raiz do projeto.

O que é uma imagem e o que é um container?#

A imagem é o pacote: sistema base, dependências e seu código, em camadas somente leitura. O container é uma instância dessa imagem em execução. Da mesma imagem você pode iniciar vários containers. O Dockerfile é a receita que descreve como montar a imagem. Para entender como containers se comparam a servidores completos, veja VPS vs plataforma de nuvem.

Como escrever o primeiro Dockerfile?#

Crie um arquivo chamado exatamente Dockerfile (sem extensão) na raiz do projeto:

dockerfile
FROM node:lts-alpine
WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci --omit=dev

COPY . .

ENV NODE_ENV=production
EXPOSE 3000
USER node
CMD ["node", "src/index.js"]

O que cada instrução faz:

  • FROM node:lts-alpine: imagem base. O Docker Hub publica a imagem oficial node com tags por versão, a tag lts (que acompanha a LTS mais recente) e variantes alpine (menor) e slim. Em produção, troque lts pela versão major que você testou, para o build não mudar sozinho.
  • WORKDIR /app: pasta de trabalho dentro da imagem. Os comandos seguintes rodam nela.
  • COPY package.json package-lock.json ./: copia só os arquivos de dependências primeiro.
  • RUN npm ci --omit=dev: instala as dependências de produção exatamente como no lockfile.
  • COPY . .: copia o restante do código.
  • ENV e EXPOSE: definem uma variável e documentam a porta que a aplicação escuta. EXPOSE não publica a porta; isso é feito no docker run.
  • USER node: a imagem oficial do Node já traz um usuário node. Rodar sem root reduz o estrago se a aplicação for comprometida.
  • CMD: o comando que roda quando o container inicia. Use a forma de lista (exec), sem passar por shell.

Por que copiar o package.json antes do resto? Cada instrução gera uma camada, e o Docker reaproveita do cache as camadas que não mudaram. Se você só alterou um arquivo .js, o npm ci não roda de novo.

Como ignorar arquivos com .dockerignore?#

COPY . . copia tudo da pasta, inclusive o que não deveria ir para a imagem. Crie um .dockerignore ao lado do Dockerfile:

node_modules
.git
.env
**/*.log
Dockerfile
.dockerignore

Isso evita levar a node_modules do seu sistema (que pode ter binários incompatíveis), o histórico do Git e, principalmente, os segredos do .env. O build fica mais rápido e a imagem, menor.

Como buildar e rodar o container?#

Gere a imagem e dê um nome (tag) a ela:

bash
docker build -t minha-app .

O ponto final é o contexto do build: a pasta enviada ao Docker. Depois rode:

bash
docker run --rm -p 3000:3000 --env-file .env minha-app
  • -p 3000:3000 liga a porta 3000 do seu computador à porta 3000 do container (host:container).
  • --env-file .env entrega as variáveis na execução, sem gravá-las na imagem. Veja mais em variáveis de ambiente.
  • --rm remove o container ao parar.

Acrescente -d para rodar em segundo plano, use docker ps para listar os containers e docker logs <id> para ver a saída.

Como saber se deu certo?#

  1. docker ps mostra o container com status Up.
  2. curl http://localhost:3000 (ou o navegador) responde como a aplicação rodando fora do Docker.
  3. docker logs <id> mostra a saída do seu programa sem erros.
  4. docker images lista a imagem e o tamanho.

Como reduzir a imagem com multi-stage build?#

Se o projeto precisa de uma etapa de build (TypeScript, bundlers), as ferramentas de build não precisam ir para a imagem final. O multi-stage usa vários FROM e copia só o resultado:

dockerfile
FROM node:lts-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:lts-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
USER node
CMD ["node", "dist/index.js"]

A primeira etapa instala tudo e compila. A segunda parte de uma base limpa, instala só as dependências de produção e recebe a pasta dist com COPY --from=build. O compilador e as devDependencies ficam de fora. O exemplo supõe que npm run build gera a pasta dist com o ponto de entrada em dist/index.js; ajuste os caminhos ao seu projeto.

Quais erros são comuns?#

O container sobe e para na hora. O processo do CMD terminou. Rode docker logs <id> e procure a causa, em geral um erro de código ou uma variável faltando.

A aplicação roda no container, mas o navegador não abre. Você esqueceu o -p, ou a aplicação escuta em 127.0.0.1. Dentro de um container ela precisa escutar em 0.0.0.0; em Node, app.listen(3000, "0.0.0.0").

Cannot find module no container. As dependências não foram instaladas na imagem, ou o .dockerignore excluiu um arquivo necessário. Reveja o COPY e o RUN npm ci.

Build lento a cada mudança. Você copiou o código antes de instalar as dependências. Mantenha a ordem: arquivos de dependências, instalação, depois o código.

Binários nativos quebram no Alpine. O Alpine usa musl em vez de glibc, e alguns pacotes com código nativo não funcionam nele. Troque a base por node:lts-slim.

Segredos na imagem. Nunca use COPY .env nem ENV TOKEN=... com valores reais. Quem tiver a imagem lê esses valores inspecionando as camadas.

Com a imagem pronta, falta decidir onde ela roda e como as versões novas chegam lá. O fluxo completo está em o que é deploy. Quando a aplicação precisar de mais de um serviço (banco, cache), o próximo passo é descrever tudo num arquivo do Docker Compose.

Perguntas frequentes

Preciso saber Linux para escrever um Dockerfile?

O básico ajuda, porque os comandos RUN rodam dentro de um sistema Linux. Para a maioria das aplicações, bastam cinco instruções (FROM, WORKDIR, COPY, RUN e CMD) e os mesmos comandos que você usa para instalar e iniciar o projeto.

Qual a diferença entre CMD e RUN?

RUN executa durante o build e o resultado fica gravado na imagem (por exemplo, instalar dependências). CMD define o comando que roda quando o container inicia.

Posso colocar o arquivo .env dentro da imagem?

Não é recomendado. Quem tiver a imagem consegue ler os segredos. Use um .dockerignore para excluir o .env e passe as variáveis na hora de rodar o container, com --env-file ou -e.

Por que meu container sobe e para logo em seguida?

Um container vive enquanto o processo principal (o do CMD) estiver rodando. Se o processo termina ou dá erro, o container para. Veja o motivo com docker logs.

Próximo artigoVPS ou hospedagem compartilhada: qual escolherHospedagem compartilhada divide um servidor entre muitos sites e é simples. A VPS dá root e isolamento de recursos, mas exige administração. Veja qual escolher.