DevOps

O Que é um Container e Por Que Isso Mudou Tudo Já leu

15 min de leitura

O Que é um Container e Por Que Isso Mudou Tudo
Durante décadas, o processo de colocar software em produção seguia um ritual previsível de imprevisibilidades. O desenvolvedor escrevia o código na sua máquina, que tinha uma versão específica do Python, uma versão espec

Durante décadas, o processo de colocar software em produção seguia um ritual previsível de imprevisibilidades. O desenvolvedor escrevia o código na sua máquina, que tinha uma versão específica do Python, uma versão específica de uma biblioteca, uma variável de ambiente configurada de um jeito particular. O código funcionava ali. Quando chegava ao servidor de produção — com outra versão do Python, outras bibliotecas, outras configurações — quebrava.

"Funciona na minha máquina" não era uma desculpa. Era um diagnóstico preciso de um problema estrutural: a ausência de um mecanismo que garantisse que o ambiente de execução fosse idêntico em qualquer lugar.

Máquinas virtuais resolviam parte do problema, mas ao custo de gigabytes de overhead por aplicação, minutos de inicialização e complexidade de gerenciamento. Era como transportar cada passageiro em um carro próprio quando um ônibus resolveria.

Os containers chegaram como o ônibus.


O Que é um Container

Um container é um processo isolado que carrega consigo tudo que precisa para executar: código, runtime, bibliotecas, variáveis de ambiente e configurações. Ele roda sobre o kernel do sistema operacional hospedeiro, mas enxerga apenas seus próprios recursos — não tem acesso ao sistema de arquivos do host, não vê os outros processos, tem sua própria interface de rede.

O isolamento é garantido por duas funcionalidades do kernel Linux:

Namespaces — criam visões isoladas de recursos do sistema. Um container tem seu próprio namespace de processos (não enxerga os processos do host), seu próprio namespace de rede (tem seu próprio IP), seu próprio namespace de sistema de arquivos.

cgroups (control groups) — limitam o quanto de CPU, memória e I/O um processo pode consumir. Um container não pode monopolizar os recursos da máquina hospedeira.


Container vs Máquina Virtual

A diferença fundamental está em onde o isolamento acontece:

┌─────────────────────────────────────┐
│         Máquina Virtual             │
│  ┌──────────┐  ┌──────────┐        │
│  │  App A   │  │  App B   │        │
│  │──────────│  │──────────│        │
│  │  Guest   │  │  Guest   │        │
│  │    OS    │  │    OS    │        │
│  └──────────┘  └──────────┘        │
│         Hypervisor                  │
│         Hardware                    │
└─────────────────────────────────────┘

┌─────────────────────────────────────┐
│            Container                │
│  ┌──────────┐  ┌──────────┐        │
│  │  App A   │  │  App B   │        │
│  │──────────│  │──────────│        │
│  │  Libs A  │  │  Libs B  │        │
│  └──────────┘  └──────────┘        │
│         Container Runtime           │
│         Host OS Kernel              │
│         Hardware                    │
└─────────────────────────────────────┘

Uma máquina virtual precisa de um sistema operacional completo para cada instância — gigabytes de disco, centenas de megabytes de RAM só para o OS. Um container compartilha o kernel do host e carrega apenas o que a aplicação realmente precisa. O resultado: containers iniciam em segundos, ocupam megabytes, e é possível rodar dezenas deles onde antes cabiam poucos VMs.

Isso não significa que containers substituem máquinas virtuais em todos os casos. Em ambientes de nuvem, os próprios containers frequentemente rodam dentro de VMs — as camadas se complementam. O que mudou foi a unidade de trabalho para empacotamento e entrega de aplicações.


O Que é uma Imagem

Um container é uma instância em execução de uma imagem. A imagem é o template — imutável, versionável, compartilhável. O container é o processo vivo criado a partir desse template.

A analogia com orientação a objetos é precisa: a imagem é a classe, o container é o objeto instanciado a partir dela.

Uma imagem é construída em camadas. Cada instrução no Dockerfile — o arquivo que descreve como construir a imagem — cria uma camada. Camadas são imutáveis e reutilizáveis: se duas imagens diferentes partem da mesma camada base, essa camada é armazenada apenas uma vez no disco.

┌─────────────────────┐
│   Camada da App     │  ← seu código
├─────────────────────┤
│  Camada das Libs    │  ← dependências instaladas
├─────────────────────┤
│  Camada do Runtime  │  ← Node.js, Python, Java...
├─────────────────────┤
│  Camada Base do OS  │  ← ubuntu:22.04, alpine:3.19...
└─────────────────────┘

O Que é o Docker

Docker é a plataforma que popularizou os containers. Lançado em 2013, ele não inventou os containers — a tecnologia subjacente de namespaces e cgroups já existia no Linux há anos — mas criou uma experiência de usuário que tornou containers acessíveis para qualquer desenvolvedor.

O Docker é composto por:

Docker Engine — o daemon que roda em segundo plano, gerenciando containers, imagens, redes e volumes.

Docker CLI — a interface de linha de comando (docker) que o usuário usa para interagir com o daemon.

Docker Hub — o registro público de imagens. É o lugar onde imagens oficiais de Nginx, PostgreSQL, Redis, Node.js e milhares de outras ferramentas estão disponíveis gratuitamente.


Por Que Isso Mudou o DevOps

Antes dos containers, o processo de deploy envolvia configurar manualmente cada servidor de destino: instalar o runtime correto, configurar variáveis de ambiente, gerenciar conflitos entre aplicações que precisavam de versões diferentes da mesma dependência.

Com containers, o que vai para produção é a imagem — e a imagem já contém tudo. O servidor de produção não precisa saber nada sobre a aplicação além de como rodar um container. O pipeline de CI/CD constrói a imagem, a testa e a envia para um registro. O servidor de produção apenas a executa.

Essa separação de responsabilidades — construir uma vez, rodar em qualquer lugar — é o que tornou possível o deploy contínuo em escala, o desenvolvimento em microsserviços e, eventualmente, a orquestração com Kubernetes.

Nos próximos artigos, essa teoria se transforma em prática: instalação, primeiros containers, Dockerfiles e composição de serviços.


O Que Vem a Seguir

No próximo artigo a teoria cede lugar à prática: instalação do Docker, primeiros comandos, execução dos primeiros containers e entendimento do ciclo de vida de um container em execução.


Referências para Aprofundamento

Documentação oficial

Leitura técnica

Prática

  • Play with Docker — Ambiente Docker gratuito no navegador, sem instalação. Ideal para explorar antes de instalar localmente.
  • Docker Getting Started — docs.docker.com — Tutorial oficial do Docker, progressivo e bem estruturado. Complementa diretamente os próximos artigos da série.

Artigo 12 de 52 · Módulo 3 — Containers com Docker Prof. Ricardo Matos · Série Dominando DevOps & Cloud em 1 Ano


you asked

Refaça o 11. O texto ficou dentro do bloco de código


claude response

Artigo 11 — Semantic Versioning e Tags de Release

Módulo 2 · Controle de Versão com Git e GitHub Prof. Ricardo Matos — Dominando DevOps & Cloud em 1 Ano


O Caos sem Versões Claras

Imagine receber uma mensagem de um colega dizendo: "o bug está na versão nova". Qual versão nova? Nova em relação a quê? Lançada quando?

Sem uma convenção de versionamento, equipes acabam usando nomes arbitrários — "versão de dezembro", "build final", "build final v2", "build final v2 revisado". Em produção, esse caos tem consequências reais: dificuldade em identificar qual código está rodando, impossibilidade de comparar comportamentos entre versões e incapacidade de comunicar claramente o impacto de uma atualização para clientes e parceiros.

O Semantic Versioning — ou SemVer — é a convenção que resolve esse problema. Adotada pela maioria dos projetos de software modernos, ela transforma um número de versão em uma comunicação precisa sobre o que mudou.


A Lógica do SemVer

Um número de versão SemVer tem o formato MAJOR.MINOR.PATCH — por exemplo, 2.4.1.

Cada parte tem um significado definido:

PATCH — incrementado quando são feitas correções de bugs compatíveis com versões anteriores. Quem usa 2.4.0 pode atualizar para 2.4.1 sem preocupação. Nada quebrou, apenas foi corrigido.

MINOR — incrementado quando novas funcionalidades são adicionadas de forma compatível com versões anteriores. Quem usa 2.4.1 pode atualizar para 2.5.0 com segurança — o que já funcionava continua funcionando.

MAJOR — incrementado quando mudanças incompatíveis são introduzidas. Quem usa 2.x precisa ler o changelog antes de atualizar para 3.0.0 — algo pode ter quebrado intencionalmente.

Regras adicionais importantes: quando MINOR é incrementado, PATCH volta a zero — 2.4.1 torna-se 2.5.0. Quando MAJOR é incrementado, MINOR e PATCH voltam a zero — 2.5.3 torna-se 3.0.0. Versões com MAJOR igual a zero (0.x.y) indicam desenvolvimento inicial, onde a API pública não é considerada estável.


Sufixos de Pré-release

Para versões que ainda não estão prontas para produção, o SemVer permite sufixos que indicam o estágio de maturidade. O alpha representa a primeira fase de testes internos, o beta indica uma fase de testes mais ampla, e o rc — release candidate — sinaliza um candidato à versão final antes da estabilização:

1.0.0-alpha
1.0.0-alpha.2
1.0.0-beta.1
1.0.0-rc.1
1.0.0

Em pipelines de CI/CD, é comum gerar versões de pré-release automaticamente para branches de desenvolvimento, enquanto versões estáveis são geradas apenas a partir de merges na main.


Tags no Git

No Git, uma tag é um ponteiro nomeado para um commit específico. Diferentemente das branches, tags não se movem — elas marcam um ponto fixo no histórico, ideal para representar releases.

# Cria uma tag leve (apenas um ponteiro)
git tag v1.2.0

# Cria uma tag anotada — recomendada, pois inclui mensagem, autor e data
git tag -a v1.2.0 -m "Release 1.2.0: adiciona suporte a autenticação OAuth"

# Lista todas as tags
git tag

# Lista tags com filtro
git tag -l "v1.*"

# Mostra detalhes de uma tag anotada
git show v1.2.0

# Cria tag em um commit específico, não necessariamente o mais recente
git tag -a v1.1.5 abc1234 -m "Hotfix: corrige falha de segurança no login"

Tags precisam ser enviadas explicitamente para o repositório remoto — o git push padrão não as inclui:

# Envia uma tag específica
git push origin v1.2.0

# Envia todas as tags locais de uma vez
git push origin --tags

Conventional Commits: A Base para Automação

O Semantic Versioning define como nomear versões. O Conventional Commits define como escrever mensagens de commit de forma que ferramentas possam determinar automaticamente qual parte da versão deve ser incrementada.

O formato é o seguinte:

<tipo>[escopo opcional]: <descrição>

[corpo opcional]

[rodapé opcional]

Os tipos mais comuns e sua relação com o SemVer:

O tipo fix indica correção de bug e resulta em incremento de PATCH:

git commit -m "fix: corrige cálculo incorreto de frete para regiões remotas"

O tipo feat indica nova funcionalidade e resulta em incremento de MINOR:

git commit -m "feat: adiciona suporte a pagamento via PIX"

Uma exclamação após o tipo, combinada com BREAKING CHANGE no rodapé, indica mudança incompatível e resulta em incremento de MAJOR:

git commit -m "feat!: remove suporte à API v1

BREAKING CHANGE: os endpoints /api/v1/* foram removidos.
Migre para /api/v2/* conforme documentação."

Os tipos que não incrementam versão são usados para mudanças que não afetam o produto em si — documentação, tarefas de manutenção, ajustes de pipeline e refatorações internas:

git commit -m "docs: atualiza exemplos de autenticação no README"
git commit -m "chore: atualiza dependências de desenvolvimento"
git commit -m "ci: adiciona cache de dependências no pipeline"
git commit -m "refactor: extrai lógica de validação para módulo separado"
git commit -m "test: adiciona testes para o módulo de pagamento"

Automatizando Releases com GitHub Actions

Com Conventional Commits no histórico, é possível automatizar completamente o processo de versionamento e geração de changelog. A ferramenta Release Please, mantida pelo Google, faz exatamente isso:

# .github/workflows/release.yml
name: Release

on:
  push:
    branches: [ main ]

permissions:
  contents: write
  pull-requests: write

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: googleapis/release-please-action@v4
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          release-type: node

O funcionamento é o seguinte: a cada push na main, o Release Please analisa os commits desde a última release, determina qual parte do SemVer deve ser incrementada com base nos tipos de commit e cria automaticamente um Pull Request com a atualização de versão e o changelog gerado. Quando esse PR é mergeado, uma tag Git e uma GitHub Release são criadas automaticamente.


Criando uma Release no GitHub Manualmente

Para equipes que preferem controle manual sobre o processo de release:

# Garante que a main está atualizada
git checkout main
git pull

# Cria a tag anotada com notas descritivas
git tag -a v2.1.0 -m "Release 2.1.0

Novidades:
- Autenticação via OAuth com Google e GitHub
- Dashboard de métricas em tempo real
- Correção de falha em sessões simultâneas"

# Envia a tag para o repositório remoto
git push origin v2.1.0

No GitHub, navega-se até Releases → Create a release, seleciona-se a tag criada e adicionam-se as notas de release. O GitHub renderiza o markdown e disponibiliza a release publicamente, com download automático do código-fonte em zip e tar.gz.


Um Fluxo Completo de Versionamento

Reunindo tudo em um cenário real, o processo segue uma sequência clara. Primeiro, a funcionalidade é desenvolvida em uma branch isolada, com commits seguindo a convenção:

git checkout -b feature/dashboard-metricas
git commit -m "feat: adiciona componente de gráfico de requisições por minuto"
git commit -m "feat: adiciona filtro por intervalo de datas no dashboard"
git commit -m "test: adiciona testes do componente de gráfico"
git push origin feature/dashboard-metricas

O PR é criado, revisado e mergeado na main. O Release Please detecta dois commits do tipo feat e propõe o incremento de MINOR — versão atual 1.4.2 torna-se 1.5.0. Quando o PR de release é mergeado, a tag v1.5.0 é criada e o changelog é gerado automaticamente:

## [1.5.0] - 2025-03-10

### Features
- adiciona componente de gráfico de requisições por minuto
- adiciona filtro por intervalo de datas no dashboard

Encerrando o Módulo 2

Com este artigo, conclui-se o Módulo 2. Foram cobertos Git no dia a dia, organização de branches com Git Flow, automação com GitHub Actions, revisão de código com Pull Requests e versionamento semântico com tags e releases automáticas.

O Módulo 3 entra no tema que mais transforma a forma de trabalhar em infraestrutura moderna: containers com Docker. Tudo que foi aprendido sobre terminal e Git será usado a partir de agora de forma integrada.


Referências para Aprofundamento

Especificações

Ferramentas

  • Release Please — GitHub — Ferramenta do Google para automação de releases baseada em Conventional Commits. Documentação completa no repositório.
  • semantic-release — Alternativa ao Release Please, mais configurável e com ecossistema de plugins. Popular em projetos Node.js.
  • git-cliff — Gerador de changelog altamente configurável escrito em Rust. Suporta templates customizados para o formato de saída.

Leitura complementar

  • Keep a Changelog — Guia e convenção para escrita manual de changelogs, disponível em português. Explica os princípios por trás de um bom changelog mesmo quando o processo não é automatizado.
Comentários

Mais em DevOps

Trabalhando em Equipe com Git Flow
Trabalhando em Equipe com Git Flow

O Git oferece branches, mas n&atilde;o diz como us&aacute;-las. Em uma equipe...

Resiliência e Chaos Engineering
Resiliência e Chaos Engineering

Todo engenheiro que opera sistemas em produção por tempo suficiente aprende u...

DevSecOps: Integrando Segurança em Todo o Pipeline
DevSecOps: Integrando Segurança em Todo o Pipeline

Durante décadas, a segurança foi tratada como uma fase — algo que acontecia d...