O Docker revolucionou a forma como desenvolvemos, empacotamos e executamos aplicações. Ele permite que você empacote uma aplicação e todas as suas dependências em um artefato padronizado chamado imagem, que pode ser executado de forma isolada em qualquer ambiente que tenha o Docker instalado. Nesta aula, vamos explorar os conceitos centrais do Docker: imagens e containers, o comando docker run, o ciclo de vida de um container e os comandos básicos para gerenciar essas entidades. Ao final, você estará apto a executar seus primeiros containers e entender como o Docker se encaixa em práticas de DevOps, como integração contínua e entrega contínua.

Antes de mergulharmos nos detalhes, é importante entender que o Docker utiliza uma arquitetura cliente-servidor. O cliente Docker (a CLI) se comunica com o daemon Docker (o servidor) que é responsável por construir, executar e distribuir os containers. Essa separação permite que você controle o Docker remotamente, mas no nosso dia a dia, usamos a CLI para interagir com o daemon local.

Imagens e containers

No Docker, uma imagem é um pacote imutável que contém tudo o que é necessário para executar uma aplicação: código, runtime, bibliotecas, variáveis de ambiente e configurações. As imagens são construídas a partir de um Dockerfile, que é um arquivo de texto com instruções para criar a imagem. Elas são armazenadas em registries, como o Docker Hub, e podem ser versionadas e compartilhadas. Uma imagem é como uma classe em programação: define o modelo.

Um container, por sua vez, é uma instância executável de uma imagem. Você pode criar, iniciar, parar, mover ou excluir um container. Ele é isolado do host e de outros containers, mas pode ser conectado a redes e volumes. Continuando a analogia, um container é como um objeto criado a partir da classe: é a materialização da imagem em execução. Múltiplos containers podem ser criados a partir da mesma imagem, cada um com seu próprio sistema de arquivos e configurações.

Vamos ver como listar imagens e containers no seu sistema:

# Listar imagens locais
docker images

# Listar containers em execução
docker ps

# Listar todos os containers (incluindo parados)
docker ps -a

No início, você verá que não há imagens locais (a menos que já tenha usado o Docker). Para obter uma imagem, você pode usar o comando docker pull, que baixa uma imagem de um registry.

# Baixar a imagem do Ubuntu mais recente
docker pull ubuntu:latest

Depois de baixada, a imagem fica disponível localmente e pode ser usada para criar containers.

docker run

O comando docker run é o comando mais importante do Docker, pois ele cria e inicia um container a partir de uma imagem. Sua sintaxe básica é:

docker run [OPÇÕES] IMAGEM [COMANDO] [ARG...]

Quando você executa docker run, o Docker verifica se a imagem existe localmente; se não, ele tenta baixá-la do registry padrão (Docker Hub). Em seguida, ele cria um container a partir dessa imagem e o inicia. Se você especificar um comando, ele será executado dentro do container; caso contrário, o comando padrão definido na imagem será usado.

Vamos executar nosso primeiro container usando a imagem hello-world:

docker run hello-world

Esse comando baixa a imagem hello-world (se necessário) e executa um container que imprime uma mensagem de boas-vindas e sai. O container é criado, executa o comando e termina. Ele não fica em execução.

Para executar um container interativo, como um shell, usamos as opções -it (interativo + TTY). Por exemplo, para abrir um shell bash dentro de um container Ubuntu:

docker run -it ubuntu:latest bash

Ao executar esse comando, você estará dentro do container com um prompt bash. Digite exit para sair e o container será encerrado.

Outras opções comuns do docker run incluem:

  • --name: atribui um nome ao container (ex.: --name meu-container).
  • -d (detached): executa o container em segundo plano, retornando o ID do container.
  • -p: publica uma porta do container para o host (ex.: -p 8080:80).
  • -v: monta um volume (ex.: -v /host/path:/container/path).
  • --rm: remove automaticamente o container quando ele for encerrado.

Exemplo de execução de um servidor web Nginx em segundo plano:

docker run -d --name meu-nginx -p 8080:80 nginx:latest

Esse comando baixa a imagem do Nginx, cria um container chamado meu-nginx, o executa em segundo plano e mapeia a porta 80 do container para a porta 8080 do host. Agora você pode acessar http://localhost:8080.

Ciclo de vida

O ciclo de vida de um container pode ser dividido em várias fases: criação, início, execução, pausa, parada e remoção. O Docker fornece comandos para gerenciar cada uma dessas fases.

Ao usar docker run, você cria e inicia o container de uma só vez. Mas também é possível criar um container sem iniciá-lo usando docker create. Isso é útil quando você quer preparar o container com antecedência e iniciá-lo depois.

# Criar um container sem iniciar
docker create --name meu-container ubuntu:latest

# Iniciar o container
docker start meu-container

Quando um container está em execução, você pode pausá-lo (congelando todos os processos) usando docker pause, e retomá-lo com docker unpause. Isso é diferente de parar o container, que encerra os processos.

# Pausar um container
docker pause meu-container

# Retomar um container pausado
docker unpause meu-container

Para parar um container, usamos docker stop, que envia um sinal SIGTERM e, após um período de graça, SIGKILL. Você pode especificar o tempo de espera com a opção -t. Já o comando docker kill envia SIGKILL imediatamente.

# Parar um container de forma graciosa (padrão 10 segundos)
docker stop meu-container

# Parar imediatamente
docker kill meu-container

Depois que o container está parado, ele ainda existe no sistema e pode ser reiniciado com docker start. Para remover definitivamente um container, use docker rm. Você pode remover containers parados ou forçar a remoção de containers em execução com a opção -f.

# Remover um container parado
docker rm meu-container

# Forçar a remoção de um container em execução
docker rm -f meu-container

O comando docker run com a opção --rm automatiza a remoção: quando o container é encerrado, ele é removido automaticamente. Isso é útil para containers temporários.

Vamos visualizar o ciclo de vida com um diagrama em texto:

+-----------+   docker create   +------------+   docker start   +------------+
|   Nada    | -----------------> |  Criado    | ----------------> |  Rodando   |
+-----------+                   +------------+                   +------------+
     ^                                |                                |
     |                                | docker stop                    | docker stop
     |                                v                                v
     |                           +------------+                   +------------+
     +--------------------------- |  Parado    | <---------------- |  Parando   |
                                  +------------+                   +------------+

Entender esse ciclo é essencial para gerenciar adequadamente seus containers em produção e em desenvolvimento.

Comandos básicos

Além dos comandos de ciclo de vida, existem outros comandos que você usará constantemente. Vamos listar os mais importantes:

  • docker ps: lista containers em execução (use -a para incluir parados).
  • docker images: lista imagens locais.
  • docker pull: baixa uma imagem do registry.
  • docker push: envia uma imagem para o registry.
  • docker exec: executa um comando dentro de um container em execução.
  • docker logs: exibe os logs de um container.
  • docker inspect: mostra informações detalhadas sobre um container ou imagem.
  • docker rm: remove um ou mais containers.
  • docker rmi: remove uma ou mais imagens.
  • docker build: constrói uma imagem a partir de um Dockerfile.

Vamos ver alguns exemplos práticos:

# Executar um comando dentro de um container em execução
docker exec -it meu-nginx bash

# Ver os logs de um container
docker logs meu-nginx

# Inspecionar um container (ex.: ver IP, portas, etc.)
docker inspect meu-nginx

# Remover uma imagem local
docker rmi ubuntu:latest

O comando docker exec é particularmente útil para depuração: você pode entrar em um container em execução e inspecionar o ambiente, processos, arquivos, etc. A opção -it é necessária para interatividade.

Outro comando útil é docker system prune, que limpa recursos não utilizados (containers parados, redes não usadas, imagens pendentes, etc.). Isso ajuda a liberar espaço em disco.

# Limpar recursos não utilizados
docker system prune

Lembre-se de que o Docker possui uma vasta documentação e que esses comandos são apenas a ponta do iceberg. Conforme avançamos, você aprenderá a criar suas próprias imagens com Dockerfile, usar Docker Compose para orquestrar múltiplos containers e integrar o Docker em pipelines de CI/CD.

Boas práticas

Ao trabalhar com Docker, siga estas boas práticas para evitar problemas:

  • Sempre nomeie seus containers (--name) para facilitar a identificação.
  • Use a opção --rm para containers temporários, evitando acúmulo de containers parados.
  • Prefira usar imagens oficiais e com tags específicas, em vez de latest, para garantir reprodutibilidade.
  • Evite executar containers como root; crie usuários não privilegiados no Dockerfile.
  • Mantenha o Docker atualizado e execute docker system prune periodicamente para limpar resíduos.

Referências

Exercícios

  1. Qual é a diferença entre uma imagem e um container no Docker? Explique com suas palavras e dê um exemplo de uso de cada um.
  2. ✓ Resposta: Uma imagem é um pacote imutável que contém o código, runtime, bibliotecas, configurações, etc. É como um modelo. Um container é uma instância executável dessa imagem, ou seja, uma realização dela em execução. Por exemplo, a imagem ubuntu define o sistema base, e quando você executa docker run ubuntu, você cria um container a partir dessa imagem, que pode ser iniciado, parado, etc.
  3. Execute o comando docker run -d --name meu-nginx -p 8080:80 nginx:latest e explique o que cada parte do comando faz.
  4. ✓ Resposta: O comando docker run cria e inicia um container. A opção -d executa em segundo plano (detached). --name meu-nginx nomeia o container como meu-nginx. -p 8080:80 publica a porta 80 do container na porta 8080 do host. nginx:latest é a imagem usada (a tag latest indica a versão mais recente).
  5. Quais são os principais comandos para gerenciar o ciclo de vida de um container? Liste e descreva brevemente cada um.
  6. ✓ Resposta: Os principais comandos são: docker create (cria um container), docker start (inicia um container existente), docker stop (para um container graciosamente), docker kill (força a parada), docker pause (pausa processos), docker unpause (retoma), docker rm (remove um container parado). Também o docker run combina criação e início.
  7. Você precisa executar um comando dentro de um container que já está em execução. Qual comando você usaria e por quê?
  8. ✓ Resposta: Usaria docker exec -it <container> <comando>. Por exemplo, docker exec -it meu-nginx bash para abrir um shell interativo. O -it permite interação e alocação de terminal. Isso é útil para depuração, inspeção de arquivos, etc.
  9. Como você faria para remover todos os containers parados e imagens não utilizadas de uma vez? Cite o comando e explique sua função.
  10. ✓ Resposta: O comando docker system prune remove containers parados, redes não utilizadas, imagens pendentes e caches de build. Para remover também imagens não utilizadas (dangling), pode-se usar docker system prune -a. Ele ajuda a liberar espaço em disco.