Docker · 35 min

Rode seus primeiros containers Docker

Baixe uma imagem oficial do Docker Hub e rode, inspecione e destrua containers reais pela linha de comando. Aprenda o modelo mental imagem-vs-container na prática — a base sobre a qual toda outra habilidade de Docker é construída.

Problema

"Funciona na minha máquina" é o bug mais antigo do software. Seu app roda localmente e quebra em staging porque a versão do Python, uma biblioteca de sistema ou uma variável de ambiente é diferente. O Docker mata essa classe de problema empacotando uma aplicação junto com tudo que ela precisa numa **imagem**, e rodando-a num **container** isolado que se comporta igual em qualquer lugar. Mas antes de conseguir containerizar seus próprios apps, você precisa de fluência no ciclo básico: achar uma imagem confiável, baixá-la, rodá-la como container, olhar dentro de um container em execução, ler seus logs e limpar tudo. Pule isso e todo tópico seguinte (Dockerfiles, Compose, redes, volumes) vira uma mágica que você não sabe depurar. Neste lab você vai rodar containers reais a partir de imagens oficiais, expor um servidor web ao seu navegador, entrar (exec) num container vivo e gerenciar o ciclo de vida completo — para que o vocabulário vire memória muscular.

Objetivos

Pré-requisitos

O que você vai construir

Você não vai escrever nenhum código de aplicação neste lab. Em vez disso, vai rodar um servidor
web real (nginx) dentro de um container, alcançá-lo pelo navegador, olhar dentro do container
em execução e depois limpar tudo — usando só a CLI do Docker. No fim, os comandos centrais
(pull, run, ps, logs, exec, stop, rm) vão parecer rotina.

O modelo mental essencial: imagem vs container

Quase toda confusão com Docker vem de embaralhar essas duas palavras. Mantenha-as afiadas:

Conceito O que é Analogia
Imagem Um pacote imutável e somente-leitura: app + runtime + bibliotecas + config, construído em camadas Uma receita, ou uma classe no código
Container Uma instância em execução de uma imagem, com sua própria camada gravável, isolada do host O bolo feito a partir da receita, ou um objeto instanciado de uma classe

Uma imagem pode gerar muitos containers, do mesmo jeito que uma classe cria muitos objetos.
A imagem nunca muda quando um container roda — o container ganha sua própria camada gravável
fina por cima. É por isso que containers são baratos de criar e descartar, e por que a mesma
imagem se comporta identicamente no seu laptop e em produção.

De onde vêm as imagens

Imagens ficam em registries. O registry público padrão é o Docker Hub
(hub.docker.com). Quando você faz docker pull nginx, o Docker busca no Docker Hub a menos que
você diga o contrário. Existem outros registries (Amazon ECR, Google GCR, Azure ACR) para
imagens privadas ou integradas à nuvem, mas o Docker Hub é onde você começa.

Como trabalhar neste lab

Faça os passos em ordem — cada comando se apoia no anterior. Rode cada comando você mesmo e leia
a saída real; não só passe o olho. No caminho você vai coletar as saídas que precisa para a
entrega do Passo 6.

Passos

  1. Confirme que o Docker está rodando e entenda a divisão

    Antes de baixar qualquer coisa, confirme que seu engine está vivo e fixe a ideia de imagem-vs-container.

    Verifique a instalação

    Rode:

    docker version
    

    Você deve ver dois blocos: Client (a CLI onde você digita) e Server / Engine
    (o daemon que de fato roda os containers). Se o bloco Server estiver faltando ou você receber
    um erro "cannot connect to the Docker daemon", inicie o Docker Desktop (Windows/macOS) ou
    rode sudo systemctl start docker (Linux) e tente de novo.

    Agora rode:

    docker info
    

    Repare na linha que informa quantas imagens e containers você tem no momento — provavelmente
    zero de cada numa instalação nova. Você vai ver esses números mudarem ao longo do lab.

    A arquitetura em uma frase

    Você digita comandos no cliente Docker; ele os envia ao daemon Docker, que baixa
    imagens de um registry e as roda como containers.

    Checkpoint

    Responda antes de continuar:

    • Se você rodar a mesma imagem três vezes, quantas imagens e quantos containers existem? (Uma imagem, três containers.)
    • Rodar um container muda a imagem de onde ele veio? (Não — a imagem é imutável; o container ganha sua própria camada gravável.)
    • De onde docker pull nginx baixa por padrão? (Do Docker Hub, o registry padrão.)

    Se alguma resposta pareceu insegura, releia "O modelo mental essencial" na visão geral. Tudo
    abaixo assume que você distingue uma imagem de um container.

  2. Baixe uma imagem oficial e fixe uma tag

    Agora busque uma imagem real. Você vai usar o nginx, um servidor web popular, porque é
    pequeno, oficial e te dá algo para abrir no navegador.

    Escolha uma imagem confiável

    No Docker Hub, prefira imagens marcadas como Official ou Verified Publisher. Imagens
    oficiais (como nginx, postgres, python) são mantidas e revisadas em segurança pelo Docker
    ou pelo projeto upstream. Imagens da comunidade podem ser ótimas, mas não são verificadas —
    cheque a data de última atualização, o número de downloads e se o Dockerfile de origem está
    publicado antes de confiar numa em algo sério.

    Baixe

    docker pull nginx:1.27-alpine
    

    Observe a saída: o Docker baixa várias camadas (layers) e reporta Pull complete para cada.
    Essas camadas são os blocos de construção da imagem — compartilhadas e cacheadas, então o
    próximo pull de uma imagem relacionada as reaproveita em vez de rebaixar.

    Por que 1.27-alpine e não só nginx

    Uma tag identifica uma versão da imagem. docker pull nginx significa implicitamente
    nginx:latest, que é um alvo móvel — muda sempre que os mantenedores publicam uma nova
    versão. Em projetos reais essa imprevisibilidade te morde: um rebuild silenciosamente pega uma
    versão diferente.

    • nginx:latest → o que for mais novo neste momento (evite em qualquer coisa que queira reproduzir)
    • nginx:1.27-alpine → fixado na linha 1.27, sobre a base pequena Alpine Linux (previsível)

    Fixar uma tag específica é o hábito mais importante para containers reproduzíveis. A variante
    -alpine também é bem menor, o que significa pulls mais rápidos e uma superfície de ataque menor.

    Verifique o que você tem

    docker images
    

    Você deve ver uma linha para nginx com a tag 1.27-alpine, um ID de imagem e um tamanho
    (o Alpine mantém em torno de ~50 MB). Seu host foi de zero imagens para uma.

    Entregável deste passo: a saída de docker images mostrando a imagem nginx presente.

  3. Rode um container e alcance-o no navegador

    Uma imagem parada ali não faz nada. Transforme-a num container em execução.

    Inicie o nginx

    docker run -d -p 8080:80 --name web nginx:1.27-alpine
    

    Leia cada flag — elas importam:

    • -d (detached) → roda em segundo plano e devolve seu prompt, em vez de sequestrar o terminal.
    • -p 8080:80 (publish) → mapeia a porta 8080 do seu host para a porta 80 dentro do container. O formato é sempre HOST:CONTAINER. O nginx escuta na 80 por dentro; você o alcança na 8080 por fora.
    • --name web → dá ao container um nome amigável para você não ter que usar o ID aleatório.
    • nginx:1.27-alpine → a imagem a instanciar.

    O Docker imprime uma longa string hexadecimal — o ID do container. Seu container está rodando.

    Veja no navegador

    Abra http://localhost:8080 — você deve ver a página "Welcome to nginx!". O tráfego bateu na
    porta 8080 da sua máquina, o Docker o encaminhou para a porta 80 no container, o nginx respondeu.
    Esse mapeamento de porta é todo o truque de expor serviços containerizados.

    Prove o isolamento

    Rode um segundo container a partir da mesma imagem numa porta de host diferente:

    docker run -d -p 8081:80 --name web2 nginx:1.27-alpine
    

    Abra http://localhost:8081 — um segundo nginx, independente, da mesma única imagem. Dois
    containers, uma imagem: exatamente o modelo da visão geral. Se você tentasse reusar a porta de
    host 8080 receberia um erro "port is already allocated", porque só um processo pode dono de uma
    porta de host.

    Entregável deste passo: anote a URL que funcionou (http://localhost:8080) e o fato de que
    dois containers rodaram de uma imagem.

  4. Inspecione um container em execução: ps, logs, exec

    Um container em execução não é uma caixa-preta. Estes três comandos são como você observa e
    depura um.

    Liste o que está rodando

    docker ps
    

    Você verá web e web2, cada um com sua imagem, status (Up ...) e o mapeamento de porta
    (0.0.0.0:8080->80/tcp). Adicione -a para ver também containers parados:

    docker ps -a
    

    Leia os logs

    Os logs de todo container são o que seu processo principal escreveu em stdout/stderr. Atualize
    http://localhost:8080 algumas vezes no navegador e então:

    docker logs web
    

    Você verá as linhas de log de acesso do nginx para as requisições que acabou de fazer. Para
    acompanhar os logs ao vivo (como tail -f), adicione -f:

    docker logs -f web
    

    Aperte Ctrl+C para parar de acompanhar — isso para de acompanhar os logs, não o container.

    Obtenha um shell dentro

    docker exec roda um comando dentro de um container já em execução. Abra um shell interativo:

    docker exec -it web sh
    
    • -i mantém a entrada aberta, -t te dá um terminal — juntos -it = uma sessão interativa.
    • sh é o shell (a imagem Alpine não inclui bash, mas tem sh).

    Agora você está dentro do container. Experimente:

    ls /usr/share/nginx/html      # a raiz web do nginx
    cat /etc/os-release           # repare que diz Alpine Linux, não o SO do seu host
    exit                          # saia do shell do container
    

    O cat /etc/os-release é o "aha": o container roda Alpine Linux independentemente do que seu
    host é. Esse é o isolamento que o Docker te dá — um ambiente autocontido empacotado na imagem.

    Entregável deste passo: a saída de docker ps e uma cópia de algumas linhas de docker logs web.

  5. Gerencie o ciclo de vida: parar, iniciar, remover, limpar

    Containers foram feitos para serem descartáveis. Pratique pará-los e removê-los para seu host ficar limpo.

    Pare e inicie

    Um container parado ainda existe — mantém sua camada gravável e pode ser reiniciado:

    docker stop web
    docker ps            # 'web' sumiu da lista de rodando
    docker ps -a         # mas ainda listado, com status 'Exited'
    docker start web     # traga de volta
    

    stop pede ao processo para desligar graciosamente; após um timeout o Docker o mata à força.
    Reiniciar reusa o mesmo container (e sua camada gravável), o que é diferente de docker run, que
    sempre cria um novo.

    Remova containers

    Para deletar um container você precisa pará-lo antes (ou forçar):

    docker stop web web2
    docker rm web web2
    

    docker rm deleta o container e sua camada gravável permanentemente. A imagem fica intacta —
    confirme:

    docker ps -a         # web e web2 sumiram
    docker images        # nginx:1.27-alpine ainda está aqui
    

    Esse é o ganho da divisão imagem/container: você jogou fora dois containers, mas a imagem (o
    blueprint reusável) permanece, pronta para gerar novos containers instantaneamente.

    Remova a imagem

    Quando você realmente não precisar mais da imagem:

    docker rmi nginx:1.27-alpine
    docker images        # de volta ao vazio
    

    Dica de limpeza

    Para recuperar espaço de todos os containers parados, redes não usadas e imagens órfãs de uma vez:

    docker system prune
    

    Ele pede confirmação e só toca em recursos não usados — seguro para limpeza, mas leia o que ele
    lista antes de digitar y.

    Entregável deste passo: confirme com docker ps -a e docker images que seu host está limpo
    (sem containers web/web2 sobrando).

  6. Entregue: documente sua execução de container

    Prove que você rodou o ciclo completo capturando a evidência num pequeno repositório.

    Monte sua entrega

    Crie uma pasta, inicialize o git e escreva um arquivo chamado SUBMISSION.md documentando o que
    você fez. Cole as saídas reais que coletou — não texto inventado.

    # Docker Lab 1 — Rode seus primeiros containers
    
    ## 1. docker images (após o pull)
    <cole sua saída de `docker images` mostrando nginx:1.27-alpine>
    
    ## 2. docker ps (com web e web2 rodando)
    <cole sua saída de `docker ps`>
    
    ## 3. Verificação no navegador
    Abri http://localhost:8080 e vi a página "Welcome to nginx!". (sim / não)
    Rodei um segundo container na 8081 a partir da mesma imagem. (sim / não)
    
    ## 4. docker logs web (algumas linhas)
    <cole algumas linhas do log de acesso>
    
    ## 5. Dentro do container
    Saída de `cat /etc/os-release` dentro do `web`:
    <cole — deve mencionar Alpine Linux>
    
    ## 6. Host limpo
    <cole `docker ps -a` e `docker images` mostrando que web/web2 sumiram>
    
    ## Reflexão (2-3 frases)
    Nas minhas palavras: a diferença entre uma imagem e um container, e por que eu fixo uma tag
    em vez de usar `latest`.
    

    Entregue

    git init
    git add SUBMISSION.md
    git commit -m "Docker lab 1 — primeiros containers"
    # faça push para um repo público ou crie um gist, depois entregue essa URL
    

    Entregue a URL do repositório ou gist como seu deliverable do lab.

    Critério de submissão (autoverificação)

    • A saída de docker images mostra a imagem nginx fixada (não latest)
    • A saída de docker ps mostra ao menos um container rodando com um mapeamento de porta HOST:80
    • Você confirmou que a página de boas-vindas do nginx carregou no navegador
    • A saída de docker logs está incluída, mostrando linhas reais de requisição
    • A saída de cat /etc/os-release mostra o SO do container (Alpine), provando que você entrou (exec) dentro
    • Um docker ps -a / docker images final mostra um host limpo
    • Sua reflexão diz corretamente, nas suas palavras, imagem vs container e por que fixar a tag ganha do latest

    O que vem a seguir

    Você já sabe rodar e gerenciar containers a partir de imagens existentes. No próximo lab de
    Docker você vai no sentido oposto: escrever um Dockerfile e construir sua própria imagem,
    transformando seu código num container portátil que você controla de ponta a ponta.