Instalando Docker no Windows com WSL: o que acontece por trás dos comandos
Quando comecei a estudar backend de forma mais séria, percebi rapidamente que programar não é só escrever código que funciona na minha máquina.
Na prática, um projeto backend depende de muita coisa ao redor da aplicação. Não basta olhar apenas para rotas, regras de negócio ou queries. Existe todo um ambiente sustentando aquele código, com ferramentas instaladas, serviços externos, configurações, permissões, processos em execução e diferenças entre sistemas.
Quanto mais eu avançava, mais percebia que muitos bugs não nasciam exatamente no código da aplicação, mas no ambiente onde ela estava rodando.
Foi aí que Docker começou a fazer sentido para mim.
Docker é uma ferramenta que permite executar aplicações em ambientes isolados e padronizados, chamados de contêineres. A grande sacada é que, em vez de depender da configuração específica da máquina de cada pessoa, conseguimos descrever um ambiente de execução e rodá-lo de forma muito mais previsível.
No fundo, não se trata de instalar uma ferramenta da moda. Trata-se de diminuir a distância entre o ambiente onde desenvolvemos, o ambiente onde testamos e o ambiente onde a aplicação vai rodar de verdade.
É uma forma de combater a famosa frase, "Na minha máquina funciona".
Quando usamos Docker corretamente, o projeto tende a se comportar de maneira mais consistente na máquina do desenvolvedor, no ambiente de integração contínua e, idealmente, em produção.
O problema que o Docker resolve
Imagine que eu estou desenvolvendo uma API em Node.js que usa PostgreSQL.
Sem Docker, cada pessoa do time precisa instalar o Node, instalar o PostgreSQL, configurar usuário, senha, porta, banco, versão correta, extensões, variáveis de ambiente e torcer para tudo ficar igual ao ambiente dos outros.
Na teoria parece simples. Na prática, cada máquina vira um pequeno universo paralelo.
Uma pessoa usa Windows. Outra usa Linux. Outra tem uma versão diferente do PostgreSQL. Outra já tem alguma coisa rodando na porta padrão. Outra instalou Node via NVM, outra via instalador, outra via gerenciador de pacotes.
Docker entra justamente para diminuir essa variação.
Ele permite empacotar e executar partes do ambiente em contêineres isolados. Esses contêineres compartilham o kernel do sistema operacional, mas rodam processos de forma separada, com seus próprios arquivos, dependências e configurações.
Isso é diferente de uma máquina virtual tradicional. Uma VM geralmente carrega um sistema operacional inteiro. Um contêiner é mais leve porque reaproveita o kernel do sistema hospedeiro e isola apenas o necessário para rodar o processo.
Só que aqui aparece uma questão importante, o Docker nasceu no mundo Linux.
Por que Docker no Windows precisa de uma camada Linux?
Docker foi projetado originalmente para usar recursos do kernel Linux. Por isso, no Windows, ele precisa de alguma camada que permita executar esse ambiente Linux por baixo.
Hoje, existem algumas formas comuns de usar Docker no Windows:
- Docker Desktop usando Hyper-V;
- Docker Desktop usando WSL 2;
- Docker Engine instalado diretamente dentro do WSL.
A escolha entre essas opções não é só estética. Ela muda a forma como o Docker roda, o peso da instalação e a proximidade com um ambiente Linux real.
Hyper-V e WSL: entendendo a diferença
O Hyper-V é uma plataforma de virtualização da Microsoft. Ele permite criar e gerenciar máquinas virtuais completas, com diferentes sistemas operacionais. É uma solução poderosa, mas também mais robusta do que eu precisava nesse momento.
Além disso, historicamente o Hyper-V completo esteve associado a edições mais avançadas do Windows, como Pro, Enterprise e Education. Para quem está estudando e quer apenas preparar um ambiente backend consistente, muitas vezes ele acaba sendo mais do que o necessário.
Já o WSL, ou Windows Subsystem for Linux, permite rodar um ambiente Linux integrado ao Windows. No caso do WSL 2, ele usa um kernel Linux real mantido pela Microsoft, com uma experiência mais leve e integrada do que gerenciar manualmente uma VM tradicional. A própria documentação da Microsoft recomenda o comando wsl --install para instalar o WSL em versões compatíveis do Windows, e novas instalações criadas por esse comando usam WSL 2 por padrão.
Essa diferença foi importante para minha decisão.
Eu não queria instalar uma solução pesada só para conseguir subir banco de dados e serviços auxiliares nos meus projetos. Eu queria um ambiente Linux simples, previsível e integrado ao meu fluxo de desenvolvimento.
Por isso, a escolha foi usar Docker Engine diretamente dentro do WSL.
Docker Engine e Docker Desktop
Antes de instalar qualquer coisa, vale separar dois conceitos que muita gente mistura, Docker Engine e Docker Desktop.
O Docker Engine é o núcleo do Docker. Ele é responsável por criar, executar e gerenciar contêineres. É o motor de verdade.
Já o Docker Desktop é uma solução mais completa. Ele inclui o Docker Engine, mas também traz interface gráfica, integrações, configurações visuais e facilidades para quem quer uma experiência mais pronta. A documentação oficial do Docker explica que o Docker Desktop no Windows pode usar o backend WSL 2, aproveitando workspaces Linux, melhor integração com o Windows e alocação dinâmica de recursos.
Mas para o meu objetivo, eu queria entender melhor o que estava acontecendo. E recomendo o mesmo para iniciantes.
Instalar o Docker Engine diretamente dentro do WSL me pareceu mais alinhado com uma filosofia de aprendizado ativo, "menos botão mágico e mais entendimento do caminho"
Não porque Docker Desktop seja ruim. Pelo contrário, ele resolve muita coisa. Mas, nesse momento, eu queria ver a instalação mais próxima do Linux, usando terminal, repositórios, pacotes e permissões.
Instalando o WSL
Antes de instalar o Docker, precisamos ter o WSL funcionando.
No PowerShell, com permissão de administrador, o comando principal é:
wsl --install
Esse comando habilita os recursos necessários do Windows, instala o WSL e, por padrão, instala uma distribuição Linux, normalmente Ubuntu. Para usar esse comando, a Microsoft informa que é necessário estar no Windows 10 versão 2004 ou superior, build 19041 ou superior, ou então no Windows 11.
Depois da instalação, é comum precisar reiniciar o computador.
Ao abrir o Ubuntu pela primeira vez, o sistema pede a criação de um usuário e senha. Esse usuário será o usuário padrão dentro do Linux. É importante lembrar essa senha, porque ela será usada em comandos com sudo
Também recomendo instalar o Windows Terminal, não porque ele seja obrigatório, mas porque deixa a experiência muito melhor. Ele permite usar PowerShell, Prompt, Ubuntu e outros shells em abas dentro da mesma interface.
Com o WSL funcionando, agora sim podemos instalar o Docker dentro do Ubuntu.
Instalando Docker Engine dentro do Ubuntu no WSL
A instalação oficial do Docker Engine no Ubuntu envolve alguns passos. A ideia geral é:
- atualizar os pacotes do sistema;
- instalar dependências necessárias;
- adicionar a chave GPG oficial do Docker;
- adicionar o repositório oficial do Docker ao APT;
- instalar os pacotes do Docker;
- permitir que o usuário rode Docker sem sudo;
- reiniciar o WSL;
- testar a instalação.
A documentação oficial do Docker recomenda instalar o Docker Engine no Ubuntu a partir do repositório oficial do Docker, e lista pacotes como docker-ce, docker-ce-cli, containerd.io, docker-buildx-plugin e docker-compose-plugin
Passo 1: atualizar o APT
Dentro do terminal do Ubuntu:
sudo apt-get update
O apt é o gerenciador de pacotes do Ubuntu. Ele é responsável por instalar, atualizar e remover programas.
Quando rodamos apt-get update, não estamos atualizando todos os programas ainda. Estamos atualizando a lista de pacotes disponíveis. É como dizer para o Ubuntu:
“Veja nos repositórios quais versões existem agora.”
Esse passo é importante porque, antes de instalar qualquer coisa, o sistema precisa saber quais pacotes estão disponíveis e de onde baixá-los.
Passo 2: instalar dependências básicas
Em seguida:
sudo apt-get install ca-certificates curl
Aqui instalamos dois pacotes importantes.
O ca-certificates permite que o sistema valide certificados digitais. Isso é essencial para conexões HTTPS confiáveis.
O curl é uma ferramenta de linha de comando usada para fazer requisições HTTP. Vamos usá-lo para baixar a chave oficial do Docker.
Estamos preparando o sistema para confiar em uma nova fonte de pacotes.
Passo 3: criar o diretório de chaves
Agora criamos o diretório onde a chave GPG do Docker será armazenada:
sudo install -m 0755 -d /etc/apt/keyrings
Esse comando cria o diretório /etc/apt/keyrings com permissão 0755
Em termos simples, estamos criando uma pasta onde o apt poderá guardar chaves usadas para verificar a autenticidade de pacotes vindos de repositórios externos.
A chave GPG funciona como uma forma de assinatura. Ela ajuda o sistema a confirmar que os pacotes baixados realmente vêm de quem dizem vir.
Passo 4: baixar a chave GPG oficial do Docker
Agora baixamos a chave:
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
Esse comando usa curl para baixar a chave GPG oficial do Docker e salvá-la no caminho /etc/apt/keyrings/docker.asc
Alguns detalhes do comando
- -f: Faz o comando falhar silenciosamente em alguns erros HTTP;
- -s: Ativa o modo silencioso;
- -S: Mostra erro caso algo dê errado.
- -L: Segue redirecionamentos.
No fundo, estamos dizendo:
“Ubuntu, esta é a chave que você deve usar para verificar os pacotes vindos do repositório oficial do Docker.”
Passo 5: ajustar a permissão da chave
sudo chmod a+r /etc/apt/keyrings/docker.asc
Esse comando muda as permissões do arquivo docker.asc, permitindo que todos os usuários do sistema consigam lê-lo.
Aqui vale entender um detalhe importante. A chave GPG não é um arquivo secreto, como uma senha. Ela funciona mais como uma chave pública usada para verificar assinaturas. O objetivo dela é permitir que o APT confirme se os pacotes baixados do repositório do Docker realmente foram assinados pelo Docker.
Quando usamos: chmod a+r, estamos dizendo:
“Todos os usuários podem ler este arquivo.”
Isso é necessário porque o processo de instalação de pacotes precisa conseguir acessar essa chave na hora de validar os pacotes. Se o APT não conseguir ler a chave, ele pode reclamar que o repositório não é confiável ou que a assinatura não pôde ser verificada.
Esse passo serve para garantir que o sistema consiga ler a chave usada para validar a autenticidade dos pacotes que virão do repositório oficial do Docker.
Passo 6: adicionar o repositório oficial do Docker
Agora vem um dos comandos mais importantes da instalação:
echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
Esse comando adiciona o repositório oficial do Docker à lista de fontes de pacotes do Ubuntu.
Por padrão, quando usamos o apt, ele busca programas nos repositórios configurados do próprio Ubuntu. Só que o Docker recomenda instalar o Docker Engine a partir do repositório oficial deles, porque ali ficam versões mais atualizadas e próprias para instalação do Docker.
Vamos quebrar o comando em partes.
dpkg --print-architecture
Esse trecho descobre a arquitetura do sistema. Em computadores mais comuns com processadores Intel ou AMD, o resultado costuma ser amd64
Apesar do nome, amd64 não significa que o processador precisa ser da AMD. Esse nome se refere à arquitetura de 64 bits usada pela maioria dos computadores modernos, incluindo máquinas com Intel.
Já arm64 é outra arquitetura, comum em alguns notebooks, servidores modernos, placas como Raspberry Pi e máquinas com processadores baseados em ARM. Essa diferença importa porque um pacote compilado para amd64 não roda corretamente em uma máquina arm64, e vice-versa.
Então, quando o comando coloca:
arch=$(dpkg --print-architecture)
ele está dizendo ao APT, “Baixe os pacotes do Docker compatíveis com a arquitetura desta máquina.”
Outra parte importante é:
signed-by=/etc/apt/keyrings/docker.asc
Isso informa que os pacotes vindos desse repositório devem ser verificados usando a chave GPG que salvamos anteriormente. Ou seja, não estamos apenas adicionando uma fonte aleatória de pacotes. Estamos dizendo qual chave deve ser usada para validar a autenticidade dela.
Agora vem este trecho:
$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Esse comando descobre o codinome da versão do Ubuntu instalada.
Cada versão do Ubuntu tem um codinome. Por exemplo, algumas versões usam nomes como jammy, focal ou noble. Isso muda de acordo com a versão do Ubuntu.
Essa informação importa porque o repositório do Docker precisa entregar pacotes compatíveis com a versão correta do sistema. Um pacote feito para uma versão do Ubuntu pode depender de bibliotecas diferentes das usadas em outra versão. Então o codinome ajuda o APT a buscar os pacotes certos para aquela distribuição.
Por fim, temos o stable
Esse termo indica que queremos usar o canal estável do repositório Docker.
Em muitos projetos, ferramentas podem oferecer canais diferentes, como versões de teste, experimentais, nightly builds ou versões estáveis. O canal stable prioriza versões consideradas prontas para uso geral. Para um ambiente de estudo e desenvolvimento backend, essa é a escolha mais segura, porque reduz a chance de instalar algo experimental que pode mudar comportamento, quebrar comandos ou trazer bugs inesperados.
No fundo, esse comando inteiro está dizendo:
“APT, adicione o repositório oficial do Docker, usando a arquitetura correta da minha máquina, a versão correta do meu Ubuntu, o canal estável e a chave GPG oficial para validar os pacotes.”
Passo 7: atualizar o APT novamente
Depois de adicionar o repositório do Docker, precisamos rodar: sudo apt-get update. Agora existe uma diferença importante. Naquele primeiro momento, o Ubuntu ainda não conhecia o repositório do Docker.
Quando adicionamos o arquivo /etc/apt/sources.list.d/docker.list estamos dizendo ao sistema que existe uma nova fonte de pacotes. Só que o APT não consulta automaticamente essa nova fonte no exato momento em que o arquivo é criado.
Passo 8: instalar os pacotes do Docker
Agora instalamos o Docker de fato:
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Esse comando instala as principais peças necessárias para usar Docker no ambiente.
O docker-ce é o Docker Engine em si, responsável por criar, executar e gerenciar contêineres. O docker-ce-cli é a interface de linha de comando, ou seja, a parte que permite usar comandos como docker ps, docker run e docker build
Também instalamos o containerd.io, que é uma camada mais baixa usada pelo Docker para lidar com a execução dos contêineres de fato. Enquanto a CLI recebe os comandos que digitamos, o Docker Engine coordena o processo e usa componentes como o containerd para baixar imagens, iniciar contêineres, parar processos e gerenciar sua execução.
O docker-buildx-plugin adiciona recursos mais modernos para construir imagens Docker, incluindo builds mais avançados e compatibilidade com diferentes arquiteturas, como amd64.
Por fim, o docker-compose-plugin permite usar Docker Compose, que é essencial em projetos backend com mais de um serviço. Em vez de subir manualmente uma API, um banco de dados e outros componentes separados, conseguimos descrever esse ambiente em um arquivo docker-compose.yml e iniciar tudo com um comando
docker compose up
Isso torna o ambiente de desenvolvimento muito mais reproduzível e próximo do que encontramos em projetos reais.
Passo 9: permitir rodar Docker sem sudo
Depois da instalação, adicionamos o usuário atual ao grupo docker:
sudo usermod -aG docker $USER
Por padrão, o Docker precisa de permissões elevadas porque ele conversa com um serviço do sistema capaz de criar redes, montar volumes, acessar recursos do kernel e controlar processos isolados.
Em sistemas Linux, esse tipo de operação não é liberado para qualquer usuário comum. Por isso, sem essa configuração, muitos comandos Docker precisam ser executados com sudo, por exemplo:
sudo docker ps
sudo docker run hello-world
O sudo significa que você está executando aquele comando com privilégios de superusuário.
O problema é que ficar usando sudo para todo comando Docker é chato e também pode atrapalhar o fluxo de desenvolvimento. Além disso, arquivos gerados por contêineres ou comandos executados com sudo podem acabar ficando com permissões de root, criando problemas depois.
Quando rodamos sudo usermod -aG docker $USER, estamos adicionando o usuário atual ao grupo docker. -aG, significa adicionar o usuário a um grupo sem removê-lo dos outros grupos aos quais ele já pertence. E $USER representa o usuário atual do terminal.
Depois disso, é necessário reiniciar a sessão para que a mudança de grupo tenha efeito. No WSL, uma forma simples é abrir o PowerShell e rodar wsl --shutdown
Depois, basta abrir o Ubuntu novamente.
A partir daí, comandos como:
docker ps
devem funcionar sem sudo.
É importante entender que isso não é apenas uma conveniência. Estamos configurando corretamente as permissões do usuário dentro do ambiente Linux para trabalhar com Docker de forma mais natural no dia a dia.
Por que escolhi esse caminho?
Eu poderia simplesmente instalar o Docker Desktop, clicar em algumas opções e seguir o tutorial. E, para muitos casos, isso funcionaria.
Mas para pessoas que estão iniciando nesses conceitos, talvez esse não seja o caminho mais interessante. Não porque Docker Desktop seja ruim, mas porque ele esconde várias partes importantes do processo. Entrega uma experiência mais pronta, mais visual e mais confortável, só que justamente por isso pode deixar invisíveis alguns conceitos fundamentais para quem está tentando entender backend de verdade.
Um bom estudante de backend não deveria ter como objetivo apenas fazer uma API subir.
O objetivo precisa ser entender o ambiente ao redor dela: o sistema operacional, as permissões, os processos, as portas, as variáveis de ambiente, os serviços em background, os bancos de dados, os arquivos de configuração e tudo aquilo que faz uma aplicação funcionar fora do editor de código.
Instalar o Docker Engine dentro do WSL obriga você a passar por conceitos que normalmente ficam escondidos:
- gerenciador de pacotes;
- repositórios do Linux;
- arquitetura do sistema;
- codinome da distribuição;
- canais de versão;
- chave GPG;
- permissões de usuário;
- grupos no Linux;
- serviços em background;
- diferença entre cliente e daemon;
- integração entre Windows e Linux.
No começo, tudo isso parece burocrático.
Parece que você está apenas copiando comandos grandes, mexendo em pastas estranhas e configurando coisas que não entende completamente. Mas é justamente esse contato com a parte “chata” que começa a construir uma noção melhor do que está acontecendo na sua máquina.