igor-millis~/research/docker-from-scratch
EN/PT
← todos os artigos

Docker do zero: o que é, por que existe e como usar

Para quem nunca usou Docker: por que “na minha máquina funciona” acontece, como um container resolve isso e o que muda em relação a uma máquina virtual. Depois, mão na massa — o primeiro container, um Dockerfile próprio, camadas, volumes e uma aplicação com Docker Compose.

§1A frase mais famosa da programação

Toda pessoa que já trabalhou com software ouviu (ou falou) isto:

"Mas na minha máquina funciona!"

A cena é sempre parecida. Você passa semanas construindo um sistema. Ele roda perfeito no seu notebook. Aí você manda para um colega, ou sobe para o servidor, e… quebra. Erro estranho, biblioteca faltando, versão errada de alguma coisa.

Por quê? Porque o seu programa não é só o seu código. Ele depende de um monte de coisa invisível ao redor:

  • a versão da linguagem (Python 3.8? 3.12?);
  • as bibliotecas instaladas (e as versões delas);
  • o sistema operacional (Windows, macOS, Ubuntu, Debian…);
  • variáveis de ambiente, arquivos de configuração, pastas que "por acaso" existiam no seu computador;
  • programas auxiliares (um banco de dados, um Redis, o ffmpeg, o libssl …).

Seu código é como uma planta: ele só cresce bem no solo certo, com a luz certa e a água certa. Quando você muda a planta de vaso, ela pode morrer — não porque a planta está errada, mas porque o ambiente mudou.

O Docker resolve exatamente isso: ele permite levar a planta junto com o vaso, a terra e o clima.

A diferença entre mandar só o código e mandar o código com o ambiente
A diferença entre mandar só o código e mandar o código com o ambiente

§2Uma história de navios (e por que o nome é "contêiner")

Antes de falar de computador, vale uma volta rápida em 1950.

Naquela época, carregar um navio era um caos. Cada mercadoria tinha um formato: sacos de café, barris de óleo, caixas de madeira, pianos, carros. Estivadores passavam dias encaixando tudo no porão como um Tetris gigante. Mercadorias quebravam, eram roubadas, e cada porto tinha um jeito diferente de trabalhar.

Aí veio uma ideia ridiculamente simples: o contêiner padronizado. Uma caixa de metal com tamanho fixo e encaixes padronizados nos cantos.

O pulo do gato não é a caixa em si — é o padrão:

  • O navio não precisa saber se dentro tem café ou piano. Ele só sabe carregar contêineres.
  • O guindaste não precisa saber. O caminhão não precisa saber. O trem não precisa saber.
  • Quem enche o contêiner se preocupa com o conteúdo. Quem transporta se preocupa só com a caixa.

O custo de transporte despencou e o comércio mundial mudou para sempre.

O Docker faz a mesma coisa com software:

Mundo dos navios Mundo do Docker
Mercadoria (café, piano…) Sua aplicação + tudo que ela precisa
Contêiner padronizado Container Docker
Navio, trem, caminhão Seu notebook, o servidor, a nuvem (AWS, Azure, GCP…)
"O navio não precisa saber o que tem dentro" "O servidor não precisa ter Python, Node, Java instalados — só Docker"

É por isso que o logo do Docker é uma baleia carregando contêineres.

§3Então, o que é o Docker, afinal?

Definição curta:

Docker é uma ferramenta que empacota uma aplicação junto com tudo que ela precisa para rodar (sistema, bibliotecas, configurações) em uma unidade isolada e portátil chamada container.

Em outras palavras, em vez de entregar "aqui está meu código, boa sorte instalando o resto", você entrega "aqui está uma caixa que já funciona; é só ligar".

Três promessas resumem o Docker:

  1. Funciona igual em qualquer lugar. Se rodou no seu notebook, roda no do colega, no servidor e na nuvem.
  2. Isolado. Cada container tem sua própria "bolha". Você pode ter um app usando Python 3.8 e outro usando Python 3.12 lado a lado, sem um saber do outro.
  3. Descartável e reproduzível. Quebrou? Apaga e cria de novo em segundos, idêntico ao original.

§4"Mas isso não é uma máquina virtual?"

Ótima pergunta — e a confusão é muito comum. A resposta é: parecido no objetivo, bem diferente no jeito.

A analogia das casas e dos apartamentos

  • Máquina virtual (VM) é como construir várias casas num terreno. Cada casa tem sua própria fundação, encanamento, rede elétrica, caixa d'água. Totalmente independente — mas cara e demorada de construir.
  • Container é como um prédio de apartamentos. Cada apartamento tem sua porta trancada, sua privacidade, seus móveis. Mas todos compartilham a fundação, a estrutura e a entrada de água do prédio. Muito mais barato e rápido de ocupar.

Tecnicamente, a "fundação compartilhada" é o kernel do sistema operacional — o núcleo que conversa com o hardware (CPU, memória, disco).

  • Uma VM simula um computador inteiro e carrega um sistema operacional completo dentro dela (com seu próprio kernel). Pesa gigabytes e leva minutos para ligar.
  • Um container reaproveita o kernel da máquina hospedeira e isola só o que importa: os arquivos, os processos, a rede. Pesa megabytes e liga em segundos (às vezes em milissegundos).
Máquina virtual versus container
Máquina virtual versus container
Máquina Virtual Container
O que carrega SO inteiro + app Só o app e suas dependências
Tamanho típico Gigabytes Megabytes
Tempo para iniciar Minutos Segundos
Quantos cabem num servidor Dezenas Centenas
Isolamento Muito forte (hardware virtual) Forte, mas compartilha o kernel

Curiosidade: no Windows e no macOS, o Docker Desktop roda uma VM Linux bem leve por baixo dos panos (no Windows, normalmente via WSL 2). Isso acontece porque a imensa maioria dos containers é feita para o kernel Linux. Você não precisa se preocupar com isso no dia a dia — mas é bom saber que existe.

Container não substitui VM em tudo. Se você precisa rodar Windows dentro de um Linux, ou quer isolamento máximo entre clientes que não confiam um no outro, VM ainda é a ferramenta certa. Na prática, é muito comum rodar containers dentro de VMs na nuvem.

§5Os 4 conceitos que você precisa dominar

Se você entender estas quatro palavras, entendeu 80% do Docker:

  1. Dockerfile
  2. Imagem
  3. Container
  4. Registry (ex.: Docker Hub)

A analogia da cozinha

Conceito Analogia O que é de verdade
Dockerfile A receita do bolo Um arquivo de texto com o passo a passo para montar o ambiente
Imagem Um bolo congelado pronto, embalado O resultado "empacotado" da receita. É só leitura, não muda
Container Uma fatia servida no prato Uma instância rodando da imagem. Você pode ter várias ao mesmo tempo
Registry O supermercado onde ficam os bolos congelados Um "repositório" online de imagens (Docker Hub, GitHub Container Registry…)

Se você programa, outra analogia ajuda muito:

Imagem está para Container assim como Classe está para Objeto. A imagem é o molde; o container é a coisa viva criada a partir do molde. De uma mesma imagem você cria quantos containers quiser.

E o fluxo completo fica assim:

Fluxo: Dockerfile vira imagem com build, imagem vira container com run, e imagens circulam pelo registry com push e pull
Fluxo: Dockerfile vira imagem com build, imagem vira container com run, e imagens circulam pelo registry com push e pull
  • docker build → transforma a receita (Dockerfile) em imagem.
  • docker run → liga um container a partir de uma imagem.
  • docker push → manda sua imagem para o registry.
  • docker pull → baixa uma imagem do registry.

Guarde esse desenho. Todo o resto é detalhe em cima dele.

§6Mão na massa: seu primeiro container

Instalando

Para conferir se deu certo, abra o terminal e rode:

docker --version

O "Olá, mundo"

docker run hello-world

Parece pouco, mas muita coisa aconteceu aqui:

  1. O Docker procurou a imagem hello-world no seu computador.
  2. Não achou, então baixou do Docker Hub (um pull automático).
  3. Criou um container a partir dela.
  4. O container rodou, imprimiu a mensagem de boas-vindas e terminou.

Esse último ponto é importante: um container vive enquanto o processo principal dele estiver rodando. O hello-world só imprime um texto e acaba — então o container acaba junto.

Um servidor web em um comando

Agora algo mais legal. Vamos subir um servidor web de verdade (o Nginx) sem instalar nada:

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

Abra o navegador em http://localhost:8080. Você vai ver a página "Welcome to nginx!".

Destrinchando o comando:

Pedaço Significado
docker run Crie e ligue um container
-d detached: rode em segundo plano e me devolva o terminal
-p 8080:80 Ligue a porta 8080 do meu computador à porta 80 do container
--name meu-site Dê um nome amigável ao container
nginx A imagem a ser usada

Agora brinque com ele:

docker ps                  # lista os containers rodando
docker logs meu-site       # mostra o que o container imprimiu
docker stop meu-site       # desliga
docker start meu-site      # liga de novo
docker rm -f meu-site      # desliga (se precisar) e apaga

Entendendo o -p 8080:80 (portas)

Lembra da analogia do prédio? Cada container é um apartamento com sua própria numeração interna. O Nginx, dentro do apartamento, atende na porta 80. Mas quem está do lado de fora (seu navegador) não enxerga o interior do apartamento.

O -p 8080:80 é como colocar uma campainha na portaria: "quem tocar o 8080 aqui embaixo, encaminha para o 80 do apartamento meu-site".

Mapeamento de portas: a porta 8080 do computador encaminha para a porta 80 do container
Mapeamento de portas: a porta 8080 do computador encaminha para a porta 80 do container

Isso permite, por exemplo, rodar três Nginx ao mesmo tempo, cada um respondendo numa porta diferente do seu computador:

docker run -d -p 8081:80 nginx
docker run -d -p 8082:80 nginx
docker run -d -p 8083:80 nginx

Todos os três usam a porta 80 por dentro — sem conflito, porque cada um está no seu próprio apartamento.

Entrando dentro do container

Quer espiar por dentro? Dá para abrir um terminal dentro de um container rodando:

docker run -d --name espiao nginx
docker exec -it espiao bash

Agora você está "dentro". Rode ls /, cat /etc/os-release… Você vai perceber que lá dentro existe um mini Linux (Debian, no caso do Nginx), mesmo que você esteja no Windows. Digite exit para sair.

Usando programas sem instalar

Este é um dos usos mais subestimados do Docker: testar coisas sem sujar seu computador.

Quer um Python 3.12 para testar algo rapidinho, mas não quer instalar?

docker run -it --rm python:3.12 python

O --rm apaga o container automaticamente quando você sair. Zero bagunça.

Quer um banco PostgreSQL para um projeto da faculdade?

docker run -d --name banco -e POSTGRES_PASSWORD=senha123 -p 5432:5432 postgres:16

Pronto, um Postgres completo rodando. Quando não precisar mais: docker rm -f banco. Nada de desinstaladores, serviços esquecidos rodando no boot ou conflitos de versão.

Sobre as tags (:3.12, :16): o que vem depois dos dois-pontos é a versão da imagem. python:3.12 e python:3.8 são imagens diferentes. Se você não especificar, o Docker usa :latest — o que é prático para brincar, mas perigoso em projetos reais, porque "latest" muda com o tempo. Em projeto sério, sempre fixe a versão.

§7Criando a sua própria imagem: o Dockerfile

Até agora só usamos imagens prontas. Mas o poder de verdade aparece quando você empacota a sua aplicação.

Vamos criar uma API mínima em Python. Crie uma pasta meu-app com três arquivos:

app.py

from flask import Flask

app = Flask(__name__)

@app.route("/")
def ola():
    return "Olá! Eu estou rodando dentro de um container"

requirements.txt

flask==3.0.3

Dockerfile (sem extensão, com "D" maiúsculo)

# 1. Comece a partir de uma imagem que já tem Python instalado
FROM python:3.12-slim

# 2. Defina a pasta de trabalho dentro do container
WORKDIR /app

# 3. Copie a lista de dependências e instale
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 4. Copie o resto do código
COPY . .

# 5. Documente a porta que o app usa
EXPOSE 5000

# 6. O comando que roda quando o container liga
CMD ["flask", "--app", "app", "run", "--host=0.0.0.0", "--port=5000"]

Lendo em português, o Dockerfile diz:

"Pegue um Linux enxuto que já vem com Python 3.12. Crie a pasta /app. Copie a lista de bibliotecas e instale. Copie meu código. Quando alguém ligar o container, rode o Flask."

Agora construa a imagem e rode:

docker build -t meu-app .
docker run -d -p 5000:5000 --name api meu-app

Acesse http://localhost:5000. Sua aplicação está rodando num container — e você nem precisa ter Python instalado no seu computador para isso funcionar. Qualquer pessoa com Docker pode rodar exatamente o mesmo ambiente.

Por que --host=0.0.0.0? Por padrão, o Flask só aceita conexões vindas "de dentro da própria máquina" (127.0.0.1). Só que a "própria máquina", para ele, é o container! Seu navegador está fora. O 0.0.0.0 diz "aceite conexões de qualquer lugar". Esse é provavelmente o erro mais comum de quem está começando — o app sobe, mas o navegador não conecta.

As instruções mais usadas

Instrução Para que serve
FROM Imagem base de onde você parte (sempre a primeira linha)
WORKDIR Pasta "atual" dentro do container
COPY Copia arquivos do seu computador para a imagem
RUN Executa um comando durante o build (ex.: instalar pacotes)
ENV Define variáveis de ambiente
EXPOSE Documenta qual porta o app usa (não publica a porta sozinho!)
CMD Comando padrão executado quando o container liga

A confusão clássica: RUN vs CMD. - RUN acontece uma vez, na hora de construir a imagem (é preparar a receita). - CMD acontece toda vez que um container liga (é servir o prato).

O arquivo .dockerignore

Assim como o .gitignore, ele diz o que não copiar para dentro da imagem. Crie na mesma pasta:

__pycache__/
.venv/
.git/
*.log
.env

Isso deixa a imagem menor, o build mais rápido e evita vazar coisas sensíveis (como o .env com senhas).

§8Camadas: o segredo da velocidade

Aqui está uma intuição que separa quem usa Docker de quem entende Docker.

Uma imagem não é um bloco único. Ela é uma pilha de camadas, como uma lasanha — e cada instrução do Dockerfile cria uma camada.

Imagem como pilha de camadas: cada instrução do Dockerfile gera uma camada; o container adiciona uma camada fina de escrita por cima
Imagem como pilha de camadas: cada instrução do Dockerfile gera uma camada; o container adiciona uma camada fina de escrita por cima

Duas consequências muito práticas:

1. Cache. Quando você roda docker build de novo, o Docker reaproveita todas as camadas que não mudaram. Ele só refaz a partir da primeira camada que mudou — e tudo que vem depois dela.

É por isso que no nosso Dockerfile copiamos o requirements.txt antes do resto do código:

COPY requirements.txt .        # muda raramente
RUN pip install -r requirements.txt   # demorado, mas fica em cache
COPY . .                       # muda toda hora

Se você editar o app.py, só a última camada é refeita. O pip install (que pode levar minutos) vem do cache. Se você fizesse COPY . . antes do pip install, qualquer alteração no código forçaria reinstalar todas as bibliotecas.

Regra de ouro: coloque no topo do Dockerfile o que muda pouco, e no final o que muda muito.

2. Compartilhamento. Se você tem dez imagens diferentes que começam com FROM python:3.12-slim, essa camada base fica uma única vez no disco. As dez imagens apontam para ela. Economia enorme de espaço e de download.

E o container? Quando você liga um container, o Docker coloca uma camada fina de escrita em cima da imagem (que é só leitura). Tudo que o container altera vai para essa camada. É por isso que você pode ter 50 containers da mesma imagem gastando quase nada de disco a mais.

…e isso nos leva a um problema importante.

§9Containers são descartáveis — e seus dados?

Aquela camada de escrita do container morre junto com ele. Se você apagar o container do Postgres, todos os dados do banco vão embora.

Isso não é um bug, é uma filosofia: containers devem ser como gado, não como animais de estimação. Você não cuida de um container específico com carinho; se ele adoecer, você troca por outro idêntico. Mas os dados precisam sobreviver.

A solução são os volumes.

A analogia do quarto de hotel

Um container é como um quarto de hotel. Quando você faz check-out, a camareira limpa tudo. Se você esqueceu algo no quarto, já era.

Um volume é como o cofre/guarda-volumes do hotel: fica fora do quarto. Você pode trocar de quarto quantas vezes quiser — suas coisas continuam guardadas.

Volumes: os dados ficam fora do container e sobrevivem quando ele é apagado
Volumes: os dados ficam fora do container e sobrevivem quando ele é apagado

Na prática:

# cria um container Postgres guardando os dados no volume "dados-banco"
docker run -d --name banco \
  -e POSTGRES_PASSWORD=senha123 \
  -v dados-banco:/var/lib/postgresql/data \
  postgres:16

O -v dados-banco:/var/lib/postgresql/data significa: "a pasta onde o Postgres guarda os dados, dentro do container, na verdade fica no volume dados-banco".

Agora você pode docker rm -f banco, criar outro container com o mesmo -v, e os dados estarão lá.

Existem dois tipos principais:

Tipo Exemplo Quando usar
Volume nomeado -v dados-banco:/caminho Dados de bancos e serviços. O Docker gerencia onde fica.
Bind mount -v ./meu-codigo:/app Ligar uma pasta do seu computador ao container. Ótimo em desenvolvimento: você edita o código no seu editor e o container vê a mudança na hora.

§10Vários containers juntos: Docker Compose

Aplicações reais raramente são um container só. Normalmente você tem algo como:

  • uma API (seu código);
  • um banco de dados (Postgres);
  • um cache (Redis);
  • talvez um frontend.

Subir tudo isso com quatro comandos docker run gigantes, lembrando de portas, volumes, senhas e redes, é chato e propenso a erro.

O Docker Compose resolve: você descreve todo o sistema num arquivo compose.yaml e sobe tudo com um comando.

Se o Dockerfile é a receita de um prato, o Compose é o cardápio do jantar inteiro: entrada, prato principal, sobremesa, e em que ordem servir.

Vamos evoluir nossa API para contar visitas usando Redis.

app.py

from flask import Flask
import redis

app = Flask(__name__)
cache = redis.Redis(host="redis", port=6379)

@app.route("/")
def ola():
    visitas = cache.incr("visitas")
    return f"Olá! Esta página foi vista {visitas} vezes."

requirements.txt

flask==3.0.3
redis==5.0.8

compose.yaml

services:
  api:
    build: .
    ports:
      - "5000:5000"
    depends_on:
      - redis

  redis:
    image: redis:7-alpine
    volumes:
      - dados-redis:/data

volumes:
  dados-redis:

E agora, a mágica:

docker compose up -d

Acesse http://localhost:5000 e recarregue a página algumas vezes. O contador sobe.

Docker Compose: um arquivo descreve a API e o Redis, que conversam por uma rede interna
Docker Compose: um arquivo descreve a API e o Redis, que conversam por uma rede interna

Repare num detalhe lindo: no código Python, conectamos no Redis usando host="redis". "redis" é o nome do serviço no compose.yaml. O Compose cria automaticamente uma rede interna onde cada serviço encontra os outros pelo nome, como se fosse um ramal telefônico interno. Repare também que o Redis não tem ports: — ele não precisa ser acessível de fora, só pela API. Mais seguro.

Comandos do dia a dia:

docker compose up -d        # sobe tudo em segundo plano
docker compose ps           # status dos serviços
docker compose logs -f api  # acompanha os logs da API
docker compose down         # desliga e remove os containers (volumes ficam)
docker compose up -d --build  # reconstrói as imagens e sobe de novo

Para um projeto novo na equipe, o onboarding vira: "clone o repositório e rode docker compose up". Sem documento de 30 passos de instalação.

§11Colinha: comandos essenciais

Imagens

docker images                 # lista imagens locais
docker pull nginx:1.27        # baixa uma imagem
docker build -t nome:tag .    # constrói uma imagem a partir do Dockerfile da pasta atual
docker rmi nome:tag           # apaga uma imagem

Containers

docker run [opções] imagem    # cria e liga um container
docker ps                     # containers rodando
docker ps -a                  # todos, inclusive parados
docker stop nome              # para
docker start nome             # liga um container parado
docker rm nome                # apaga (precisa estar parado; use -f para forçar)
docker logs -f nome           # acompanha os logs
docker exec -it nome sh       # abre um terminal dentro do container

Opções mais usadas do docker run

Opção Significado
-d Roda em segundo plano
-it Modo interativo com terminal
--rm Apaga o container quando ele terminar
-p HOST:CONTAINER Publica uma porta
-v ORIGEM:DESTINO Monta um volume ou pasta
-e CHAVE=valor Define variável de ambiente
--name nome Dá um nome ao container

Faxina

docker system df              # quanto espaço o Docker está usando
docker system prune           # remove containers parados, redes sem uso e imagens órfãs

docker system prune -a --volumes é a "bomba nuclear": apaga também imagens não usadas e volumes — ou seja, pode apagar dados de banco. Use sabendo o que está fazendo.

§12Erros comuns de quem está começando

"Rodei o container e ele morreu na hora." Lembre: o container vive enquanto o processo principal vive. Rode docker logs nome-do-container para ver o erro. Muitas vezes é um comando errado no CMD ou uma variável de ambiente faltando.

"O app está rodando, mas o navegador não conecta." Duas causas campeãs: (1) você esqueceu o -p para publicar a porta; (2) o app está escutando em 127.0.0.1 em vez de 0.0.0.0 dentro do container.

"Apaguei o container e perdi meus dados." Faltou volume. Tudo que precisa sobreviver deve ir para um volume.

"Mudei o código e nada mudou." A imagem é uma foto do momento do build. Mudou o código? Precisa de docker build de novo (ou docker compose up --build). Em desenvolvimento, um bind mount evita isso.

"No container, localhost não acha meu banco." Dentro de um container, localhost é o próprio container, não o seu computador. Use o nome do serviço (com Compose) ou host.docker.internal para alcançar a máquina hospedeira (no Docker Desktop).

"Meu disco está cheio." Imagens e containers antigos se acumulam. docker system df mostra o tamanho; docker system prune limpa.

§13Boas práticas (o "nível seguinte")

  • Fixe versões. FROM python:3.12-slim em vez de FROM python. Reprodutibilidade é o motivo de usar Docker, afinal.
  • Prefira imagens enxutas. Variantes -slim e -alpine são bem menores. Menos coisa instalada = imagem mais leve e menos superfície para falhas de segurança.
  • Ordene o Dockerfile pensando no cache. O que muda pouco em cima, o que muda muito embaixo.
  • Use .dockerignore.
  • Nunca coloque senhas dentro da imagem. Nada de ENV SENHA=... no Dockerfile nem copiar o .env para a imagem. Passe segredos na hora de rodar (-e, env_file no Compose, ou ferramentas de segredos).
  • Um processo principal por container. A API num container, o banco em outro. Não tente enfiar tudo numa caixa só — combine-os com Compose.
  • Não rode como root quando der. Muitas imagens permitem definir um usuário comum com a instrução USER. É uma camada extra de segurança.

§14O que o Docker não é

Para fechar o mapa mental, vale desfazer alguns mitos:

  • Não é uma linguagem de programação. É uma ferramenta de empacotamento e execução.
  • Não é uma máquina virtual (como vimos, compartilha o kernel).
  • Não é só para "DevOps". Cientistas de dados, estudantes e desenvolvedores de qualquer área se beneficiam dele.
  • Não é obrigatório para tudo. Um script de 20 linhas que só você usa provavelmente não precisa de container. Docker brilha quando há dependências, várias pessoas ou vários ambientes envolvidos.
  • Não é o mesmo que Kubernetes. O Docker roda containers numa máquina. O Kubernetes orquestra milhares de containers em muitas máquinas (decide onde cada um roda, reinicia os que caem, escala conforme a demanda). Se Docker é o contêiner, Kubernetes é a administração do porto inteiro.

§15Resumindo tudo em um parágrafo

Seu software depende de um ambiente, e ambientes são diferentes em cada máquina. O Docker resolve isso empacotando o app junto com o ambiente. Você escreve uma receita (Dockerfile), constrói um pacote imutável (imagem), e liga quantas cópias quiser desse pacote (containers), que são leves porque compartilham o kernel do sistema hospedeiro. Imagens são feitas de camadas reaproveitáveis, circulam por registries como o Docker Hub, guardam dados importantes em volumes, expõem serviços por portas e, quando o sistema tem várias peças, são orquestradas juntas pelo Docker Compose.

Se você entendeu este parágrafo, você saiu do zero.

§16Próximos passos

  1. Pratique. Pegue um projeto seu (da faculdade, do trabalho, um side project) e escreva um Dockerfile para ele.
  2. Explore o Docker Hub. Veja as imagens oficiais de bancos, linguagens e ferramentas que você já usa.
  3. Leia a documentação oficial, que é excelente: docs.docker.com/get-started.
  4. Aprenda multi-stage builds — uma técnica para gerar imagens muito menores separando o ambiente de compilação do de execução.
  5. Quando se sentir confortável, dê uma olhada em CI/CD (rodar testes em containers a cada commit) e, depois, em Kubernetes.

§17Glossário rápido

Termo Em uma frase
Container Uma instância isolada e em execução de uma imagem.
Imagem Um pacote imutável com app + dependências, usado para criar containers.
Dockerfile Arquivo de texto com as instruções para construir uma imagem.
Camada (layer) Cada "andar" de uma imagem; reaproveitada entre builds e entre imagens.
Registry Repositório remoto de imagens (ex.: Docker Hub).
Tag A "versão" de uma imagem (o que vem depois dos :).
Volume Armazenamento que vive fora do container e sobrevive a ele.
Bind mount Uma pasta do seu computador "espelhada" dentro do container.
Docker Compose Ferramenta para definir e subir vários containers juntos com um arquivo YAML.
Kernel Núcleo do sistema operacional; containers compartilham o do hospedeiro.
Docker Desktop Aplicativo para Windows/macOS que instala e roda o Docker (com uma VM Linux leve por baixo).

Referências: documentação oficial do Docker (guia de introdução, referência do Dockerfile e do Docker Compose); Docker Hub. Diagramas feitos por mim para este artigo.