O que é Docker? Containers explicados de forma simples
Descubra o que são containers, como o Docker funciona por baixo dos panos (namespaces, cgroups e camadas) e por que ele acabou de vez com o clássico problema do na minha maquina funciona.

"Na minha máquina funciona" foi por anos a frase mais frustrante do desenvolvimento de software. O Docker surgiu para enterrar essa desculpa de vez, empacotando aplicações junto com tudo de que precisam para rodar. Neste artigo você vai entender o que são containers, como o Docker funciona por baixo dos panos e por que ele revolucionou a forma como entregamos software — passando por namespaces, cgroups, volumes, redes, Docker Compose, segurança e troubleshooting.
O problema que o Docker resolve
Um programa raramente roda sozinho. Ele depende de uma versão específica da linguagem, de bibliotecas, de variáveis de ambiente e de configurações do sistema operacional. Quando esse conjunto difere entre a máquina do desenvolvedor e o servidor de produção, surgem bugs misteriosos — aqueles que só aparecem "lá" e nunca "aqui".
As soluções antigas eram trabalhosas:
O Docker propôs um meio-termo: empacotar a aplicação e suas dependências em uma unidade leve e portátil chamada container (Merkel, 2014). Em vez de virtualizar o hardware e carregar um sistema operacional inteiro por aplicação, ele isola processos dentro do mesmo kernel do sistema host. O resultado é dramático: containers sobem em segundos, pesam megabytes em vez de gigabytes, e você consegue rodar dezenas deles na mesma máquina sem o desperdício de múltiplos sistemas operacionais.
Além do desperdício de recursos, as VMs não resolviam bem o problema da consistência de ambiente. Você ainda precisava configurar manualmente cada VM. O Docker transforma essa configuração em um arquivo versionável e reproduzível, eliminando a divergência entre desenvolvimento, teste e produção.
O que é um container
Um container é um pacote isolado que contém sua aplicação e tudo de que ela precisa para funcionar: código, bibliotecas, dependências e configurações. Ele roda de forma isolada do resto do sistema, mas compartilha o núcleo (kernel) do sistema operacional da máquina hospedeira.
É essa partilha do kernel que torna o container tão eficiente. A diferença para uma VM é fundamental:
Na prática, você pode rodar dezenas de containers na mesma máquina onde caberiam apenas algumas VMs. Por compartilhar o kernel, um container Linux precisa rodar sobre um kernel Linux. É por isso que, no Windows e no macOS, o Docker Desktop roda uma pequena máquina virtual Linux nos bastidores — os containers Linux vivem dentro dela.
Como os containers funcionam por baixo dos panos
Containers não são mágica nem uma tecnologia totalmente nova: eles são construídos sobre recursos que já existem no kernel do Linux há anos. Entender esses dois pilares ajuda a desmistificar o Docker.
Namespaces: o isolamento
Os namespaces são o recurso do kernel responsável por dar a cada container a ilusão de que ele tem o sistema só para si. Cada tipo de namespace isola uma faceta diferente:
Cgroups: o controle de recursos
Enquanto os namespaces dizem o que um container pode ver, os control groups (cgroups) controlam quanto ele pode consumir. É por meio dos cgroups que você limita um container a, digamos, no máximo 512 MB de memória ou meio núcleo de CPU. Isso evita que um único container descontrolado derrube toda a máquina ao engolir todos os recursos.
A combinação de namespaces (isolamento) e cgroups (limites) é o que torna possível rodar muitos containers lado a lado de forma segura e previsível. O Docker é, em boa parte, uma camada amigável de ferramentas e formatos sobre esses mecanismos do kernel.
Imagens e containers: a diferença
Dois conceitos costumam confundir iniciantes: imagem e container.
De uma mesma imagem você pode criar vários containers idênticos, da mesma forma que várias casas seguem a mesma planta. Outra analogia útil é a da culinária: a imagem é a receita, e o container é o prato pronto que você cozinhou a partir dela.
Camadas: o segredo da eficiência
As imagens são montadas em camadas (layers) empilhadas: cada instrução de construção adiciona uma camada, e camadas iguais são reaproveitadas entre imagens, economizando espaço e tempo. Essas camadas são imutáveis: se duas imagens partem da mesma base, elas compartilham as camadas comuns no disco, sem duplicação.
Esse modelo de camadas também alimenta o cache de build. Quando você reconstrói uma imagem, o Docker reaproveita as camadas que não mudaram e só refaz a partir do ponto em que houve alteração. É por isso que a ordem das instruções no Dockerfile importa tanto: colocar primeiro o que muda menos (a instalação de dependências) e depois o que muda mais (o seu código) torna os rebuilds muito mais rápidos.
O Dockerfile: receita da sua imagem
A imagem é construída a partir de um arquivo de texto chamado Dockerfile, que funciona como uma receita passo a passo. É um artefato de código que deve ser versionado junto com a aplicação. Veja um exemplo simples para uma aplicação Node.js:
# Parte de uma imagem base já pronta
FROM node:20
# Define a pasta de trabalho dentro do container
WORKDIR /app
# Copia e instala as dependências
COPY package*.json ./
RUN npm install
# Copia o restante do código
COPY . .
# Expõe a porta e define o comando inicial
EXPOSE 3000
CMD ["node", "index.js"]Com esse arquivo, qualquer pessoa consegue gerar exatamente a mesma imagem. Esse versionamento da infraestrutura combina muito bem com o versionamento do código no O que é Git? Controle de versão para iniciantes: o Dockerfile vive junto do projeto e evolui com ele.
Repare na ordem: primeiro copiamos apenas o package*.json e instalamos as dependências, e só depois copiamos o restante do código. Enquanto você só altera o seu código (e não as dependências), o Docker reaproveita a camada do npm install em cache, economizando muito tempo a cada build.
Vale conhecer as principais instruções:
.dockerignore: não copie o que não precisa
Assim como o .gitignore, o arquivo .dockerignore diz ao Docker quais arquivos e pastas devem ser ignorados ao construir a imagem. Isso evita enviar lixo para dentro da imagem (como node_modules local, logs ou a pasta .git), reduz o tamanho final e acelera o build:
node_modules
npm-debug.log
.git
.env
distEsquecer o .dockerignore é um erro comum: sem ele, você pode acabar copiando centenas de megabytes desnecessários para dentro da imagem e até vazar segredos (como um arquivo .env) por descuido.
Multi-stage builds: imagens menores e mais seguras
Um dos recursos mais valiosos do Docker são as construções em múltiplos estágios. A ideia é usar uma imagem maior, com todas as ferramentas de build, para compilar a aplicação, e depois copiar apenas o resultado final para uma imagem enxuta. Assim, a imagem que vai para produção não carrega compiladores, dependências de desenvolvimento nem código-fonte desnecessário.
# Estágio 1: build
FROM node:20 AS build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# Estágio 2: imagem final, enxuta
FROM node:20-slim
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY package*.json ./
RUN npm install --omit=dev
EXPOSE 3000
CMD ["node", "dist/index.js"]O resultado é uma imagem final menor, mais rápida de transferir e com menor superfície de ataque, já que tudo o que era necessário apenas para construir ficou para trás no primeiro estágio.
Comandos essenciais do Docker
O fluxo básico do Docker gira em torno de poucos comandos:
# Constrói uma imagem a partir do Dockerfile
docker build -t minha-app .
# Cria e executa um container a partir da imagem
docker run -p 3000:3000 minha-app
# Lista os containers em execução
docker ps
# Para um container
docker stop <id>Vale entender cada um:
É comum que essa aplicação exponha uma O que é uma API? Definição clara para iniciantes, consumida por outros serviços ou pelo front-end. Outros comandos muito úteis no dia a dia:
docker images # lista as imagens locais
docker rm <id> # remove um container parado
docker rmi <imagem> # remove uma imagem
docker logs <id> # mostra os logs de um container
docker exec -it <id> bash # abre um shell dentro do container em execuçãoVolumes e persistência de dados
Containers são, por natureza, efêmeros: quando você remove um container, tudo o que foi escrito no seu sistema de arquivos interno desaparece junto. Isso é ótimo para reproducibilidade, mas péssimo quando você precisa guardar dados (de um banco de dados, por exemplo).
A solução são os volumes. Um volume é um espaço de armazenamento gerenciado pelo Docker que vive fora do ciclo de vida do container, persistindo mesmo que o container seja destruído e recriado:
# Cria um volume nomeado
docker volume create dados-db
# Monta o volume em um diretório do container
docker run -v dados-db:/var/lib/postgresql/data postgres:16Há também os bind mounts, que mapeiam um diretório do seu host diretamente para dentro do container. Eles são muito usados em desenvolvimento, para que alterações no código no host reflitam instantaneamente dentro do container:
docker run -v $(pwd):/app minha-appA regra prática é: use volumes nomeados para dados que precisam persistir (bancos de dados, uploads) e bind mounts para o fluxo de desenvolvimento local.
Redes no Docker
Por padrão, cada container roda em uma rede isolada. Para que vários containers conversem entre si — por exemplo, sua aplicação acessando um banco de dados em outro container — o Docker oferece redes (networks). Ao colocar dois containers na mesma rede definida pelo usuário, eles passam a se enxergar pelo nome do serviço, sem precisar saber o endereço IP um do outro:
docker network create minha-rede
docker run --network minha-rede --name banco postgres:16
docker run --network minha-rede --name api minha-appNesse exemplo, a aplicação api pode se conectar ao banco simplesmente usando o hostname banco. Esse mecanismo de descoberta por nome é a base para conectar os diversos serviços de uma aplicação distribuída.
Docker Compose: orquestrando vários containers
Subir um container por vez na mão funciona para testes rápidos, mas aplicações reais costumam ter vários serviços: a aplicação, o banco de dados, um cache, uma fila. Gerenciar isso comando a comando vira um pesadelo. É aí que entra o Docker Compose.
O Compose permite descrever todos os serviços, redes e volumes da sua aplicação em um único arquivo YAML, e subir tudo com um comando só:
services:
api:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgres://user:senha@banco:5432/app
depends_on:
- banco
banco:
image: postgres:16
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=senha
- POSTGRES_DB=app
volumes:
- dados-db:/var/lib/postgresql/data
volumes:
dados-db:Com esse arquivo docker-compose.yml, você sobe a stack inteira com docker compose up e derruba tudo com docker compose down. O Compose cria automaticamente uma rede compartilhada, conecta os serviços pelos seus nomes (a api acessa o banco pelo host banco) e cuida da ordem de inicialização com depends_on. Para desenvolvimento local, é a forma mais prática de reproduzir um ambiente completo.
Registries: compartilhando imagens
Depois de construir uma imagem, você pode publicá-la em um registry, um repositório de imagens. O mais conhecido é o Docker Hub, mas existem muitos outros (GitHub Container Registry, Amazon ECR, Google Artifact Registry) e empresas costumam ter registries privados.
docker push minha-empresa/minha-app:1.0
docker pull minha-empresa/minha-app:1.0Isso permite que qualquer máquina baixe e rode exatamente a mesma imagem. Repare na tag :1.0 no final do nome: as tags identificam versões da imagem. É uma boa prática evitar depender da tag latest em produção, porque ela é móvel e pode apontar para imagens diferentes ao longo do tempo, quebrando a reproducibilidade. Prefira tags explícitas e imutáveis (por número de versão ou hash de commit).
É justamente esse poder de reprodutibilidade que faz o Docker brilhar em automação de testes e deploy, o tema do O que é CI/CD? Integração e entrega contínua: o pipeline constrói a imagem uma vez, roda os testes dentro dela e a promove, idêntica, por todos os ambientes.
Docker e arquiteturas modernas
A leveza dos containers casou perfeitamente com uma tendência arquitetural: dividir grandes aplicações em serviços menores e independentes. Cada serviço vira um container próprio, com seu ciclo de vida e suas dependências isoladas. Esse modelo é detalhado em O que são microsserviços? Vantagens, desafios e quando usar.
Mas rodar muitos containers em produção traz novos desafios: como escalá-los, reiniciá-los quando caem e distribuí-los entre várias máquinas? É aí que entra a orquestração, explicada em O que é Kubernetes? Orquestração de containers. A própria evolução dos sistemas de orquestração na indústria nasceu da experiência do Google em operar containers em escala massiva (Burns et al., 2016).
Em resumo: o Docker resolve o empacotamento e a execução de um container; o Kubernetes resolve a coordenação de muitos containers em produção. São camadas complementares.
Segurança em containers
Containers oferecem isolamento, mas não são uma barreira de segurança absoluta — afinal, eles compartilham o kernel do host. Alguns cuidados reduzem bastante os riscos:
Troubleshooting: quando algo dá errado
Mais cedo ou mais tarde, um container vai falhar. Estes são os primeiros comandos a usar para investigar:
Um erro clássico é o container que "sobe e morre" imediatamente. Geralmente isso acontece porque o processo principal terminou (um container vive enquanto o seu processo de PID 1 estiver rodando) ou porque houve uma falha de configuração. O docker logs costuma revelar a causa em segundos.
Vantagens de adotar containers
Resumindo os ganhos que o Docker trouxe:
Boas práticas ao usar Docker
Para tirar o máximo proveito, vale seguir alguns cuidados:
Perguntas frequentes
Docker substitui as máquinas virtuais? Não totalmente. Containers e VMs resolvem problemas parecidos com trade-offs diferentes. VMs oferecem isolamento mais forte (kernel próprio) e são necessárias quando você precisa rodar sistemas operacionais diferentes ou exige uma fronteira de segurança mais rígida. Na prática, é comum rodar containers dentro de VMs, combinando os dois.
Preciso saber Linux para usar Docker? Ajuda muito, mas não é pré-requisito para começar. Como containers Linux rodam sobre o kernel Linux, conhecer comandos básicos de terminal e de sistema de arquivos torna o aprendizado bem mais suave. No Windows e no macOS, o Docker Desktop cuida da camada Linux por baixo.
Qual a diferença entre Docker e Docker Compose? O Docker gerencia containers individualmente. O Docker Compose é uma ferramenta para definir e orquestrar vários containers de uma aplicação a partir de um único arquivo YAML, ideal para desenvolvimento local com múltiplos serviços.
Docker e Kubernetes são concorrentes? Não, são complementares. O Docker cuida de construir e rodar containers; o Kubernetes orquestra muitos containers em produção, em múltiplas máquinas, cuidando de escala, recuperação de falhas e distribuição de tráfego.
Containers são seguros? São razoavelmente isolados, mas compartilham o kernel do host, então não substituem uma fronteira de segurança forte por si só. Seguindo boas práticas — não rodar como root, usar imagens mínimas, escanear vulnerabilidades e não embutir segredos — você reduz bastante os riscos.
Por que meu container morre logo depois de subir? Quase sempre porque o processo principal (PID 1) terminou ou falhou. Um container só vive enquanto seu processo principal estiver rodando. Use docker logs <id> para ver a causa e docker ps -a para checar o código de saída.
Conclusão
O Docker resolveu um problema antigo e custoso ao empacotar aplicações em containers leves, isolados e portáteis. Por trás da simplicidade da interface estão recursos do kernel Linux — namespaces e cgroups — que oferecem isolamento eficiente, muito mais barato que as máquinas virtuais tradicionais. Entendendo a diferença entre imagens e containers, o papel do Dockerfile, os volumes, as redes e os comandos básicos, você já consegue rodar suas aplicações de forma consistente em qualquer lugar.
A partir daí, o Docker abre as portas para microsserviços, pipelines de CI/CD e orquestração em larga escala — os pilares do desenvolvimento moderno. Comece pequeno: escreva um Dockerfile para um projeto seu, suba-o com docker run, experimente o Docker Compose com um banco de dados, e veja a mágica do "funciona em qualquer máquina" acontecer.