TECNOLANDIA TECH

Docker: Funciona na Minha Máquina

Docker: Funciona na Minha Máquina

Fonte: Unsplash

Deu erro no computador do colega. Você abre, roda o mesmo comando e funciona. A frase nasce sozinha: "funciona na minha máquina". Todo mundo ri na primeira vez; na décima vez, ela já custou uma tarde de reunião, três comentários no pull request e um deploy adiado. Docker não nasceu de uma ideia de marketing — nasceu justamente dessa piada que deixou de ser engraçada.

Onde mora a diferença entre duas máquinas

Dois computadores idênticos na nota fiscal não rodam o mesmo ambiente. As divergências costumam vir de quatro lugares:

  • Sistema operacional e versão. Quem está no Windows tenta reproduzir um problema que só aparece no Linux, ou o contrário. Flags de compilação, caminhos de arquivo e até nomes de variáveis de ambiente mudam.
  • Versões de runtime. Node 18 em uma máquina, Node 20 na outra. Python 3.11 contra 3.12. Uma minor release já muda comportamento de biblioteca.
  • Dependências implícitas. O famoso "lá eu já tinha isso instalado" — bibliotecas de sistema, ferramentas de build, fontes de dados, permissões.
  • Configuração escondida. Variáveis de ambiente no perfil do shell, arquivo de configuração fora do repositório, cache de pacote desatualizado.

O detalhe incômodo: nenhuma dessas diferenças aparece no seu código. Elas vivem fora do repositório, no estado da máquina — e é exatamente aí que a reproducibilidade morre.

A imagem como contrato de execução

Container resolve trocando a pergunta "está instalado na sua máquina?" por "a imagem foi construída?". Uma imagem Docker empacota o sistema base, as versões de runtime, as dependências e o comando de inicialização. Todo mundo que roda a mesma imagem recebe o mesmo conteúdo, no seu computador, no do colega e no servidor de produção.

Ou seja: o ambiente deixa de ser conhecimento pessoal e vira artefato versionado no repositório. Quem clona o projeto e roda o build tem o mesmo setup — porque o setup está escrito, não memorizado.

Da reclamação ao artefato: o mínimo que resolve

Para a maioria das aplicações, o contrato inteiro cabe em poucas linhas:

FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]

Repare na ordem: primeiro as dependências (cacheiam e não invalidam a cópia do código), depois o código. Um arquivo assim já mata três das quatro fontes de divergência citadas acima.

Com o arquivo na raiz do projeto, o fluxo vira rotina:

docker build -t meu-app:1.0 .
docker run --rm -p 3000:3000 --env-file .env meu-app:1.0

Quando há mais de um serviço (aplicação, banco, cache), o docker-compose.yml descreve o time inteiro em um documento só: quem sobe junto, quais portas, qual volume guarda o dado. Duas pessoas do time executam um mesmo comando e ficam com o mesmo sistema — inclusive as versões do banco, que costumam ser a origem silenciosa do "só quebra aqui".

services:
  api:
    build: .
    ports: ["3000:3000"]
    env_file: .env
  db:
    image: postgres:16
    volumes: ["dbdata:/var/lib/postgresql/data"]
volumes:
  dbdata:

Detalhe que quase todo mundo esquece: um .dockerignore evitando node_modules, .git e arquivos locais mantém a imagem limpa e o build igual para todos.

O fluxo que elimina a desculpa de vez

  1. Escriva o Dockerfile no mesmo PR que introduce a dependência nova. Quem adiciona biblioteca também responde por como ela instala.
  2. Construa a imagem no CI e publique com tag imutável (hash ou número de build), não apenas latest.
  3. Rode testes dentro do container, não no runner host — senão você validou o ambiente errado.
  4. Deploy a partir da imagem já testada. O artefato que passou no CI é o mesmo que entra em produção, sem recompilar.
  5. Documente o único comando de subida no README. Se continuar havendo passo manual, a desculpa continua viva.

Configuração e segredo: o que não pode entrar na imagem

Ambiente igual não quer dizer configuração igual. Segredo (chave de API, string de banco) nunca deve ser copiado para dentro da imagem: deixe-o para passar em tempo de execução, por variável de ambiente ou pelo mecanismo de secrets do seu orquestrador. É por isso que o exemplo acima usa --env-file em vez de copiar o arquivo no Dockerfile.

Outra armadilha: caminho absoluto escrito no código e biblioteca que assume diretório específico do sistema. Quando o container roda em diretório de trabalho diferente, o erro aparece só em produção. Testar a imagem limpa — sem cache, em máquina nova — é a única forma honesta de confirmar que o ambiente está realmente declarado, e não apenas escondido no seu histórico de shell.

O que o container não conserta

Container isola dependências, não isola problemas de projeto. Configuração errada dentro da imagem continua errada; integração mal feita entre serviços continua mal feita; e dado corrompido em volume local não é resolvido por recriar o container. Além disso, imagens base precisam de atualização de segurança — reproducibilidade não é o mesmo que imutabilidade eterna.

A lição prática é simples: pare de tratar "funciona na minha máquina" como piada e comece a tratá-la como bug de processo. Assim que o ambiente vira arquivo versionado, a pergunta deixa de ser sobre a sua máquina e passa a ser sobre o que o time acordou executar.

E aí, fez sentido?

Conte nos comentários a pior briga de ambiente que você já viveu (e como resolveu) — e se o artigo te ajudou, compartilhe com alguém que precisa ler. 👇