§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, olibssl…).
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.
§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:
- Funciona igual em qualquer lugar. Se rodou no seu notebook, roda no do colega, no servidor e na nuvem.
- 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.
- 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 | 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:
- Dockerfile
- Imagem
- Container
- 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:
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
- Windows / macOS: instale o Docker Desktop. No Windows, ele vai pedir para ativar o WSL 2 — aceite.
- Linux: siga o guia oficial da sua distribuição em docs.docker.com/engine/install.
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:
- O Docker procurou a imagem
hello-worldno seu computador. - Não achou, então baixou do Docker Hub (um
pullautomático). - Criou um container a partir dela.
- 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".
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.
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.
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.
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-slimem vez deFROM python. Reprodutibilidade é o motivo de usar Docker, afinal. - Prefira imagens enxutas. Variantes
-slime-alpinesã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.envpara a imagem. Passe segredos na hora de rodar (-e,env_fileno 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
- Pratique. Pegue um projeto seu (da faculdade, do trabalho, um side project) e escreva um Dockerfile para ele.
- Explore o Docker Hub. Veja as imagens oficiais de bancos, linguagens e ferramentas que você já usa.
- Leia a documentação oficial, que é excelente: docs.docker.com/get-started.
- Aprenda multi-stage builds — uma técnica para gerar imagens muito menores separando o ambiente de compilação do de execução.
- 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.