Continuando de onde paramos na nossa última aula, vamos entrar nos dois principais conceitos do Docker: Imagens e Containers.
O que é uma Imagem?
Já fazendo um gancho com a aula passada, nós aprendemos que o Docker é útil pois ele lê uma especificação para conseguir criar a mesma máquina igual sempre.
Isso significa que para criar um container, alguém precisa ter escrito a especificação antes. Ela é utilizada pelo Docker, que segue as suas instruções para sempre criar o container da mesma maneira.
Se você já aprendeu a programar de forma orientada a objetos é como a diferença entre uma classe (imagem) e um objeto (container).
A imagem é essa especificação que fazemos para o Docker criar os containers. Ela pode ser um arquivo local para criarmos nossas próprias imagens, ou pode ser uma já pronta vinda da internet igual uma biblioteca externa.
No exercício de Hello World que fizemos, puxamos a imagem de um repositório externo ao invés de criar manualmente.
O que é um Container
O Container é a máquina virtual criada de fato depois que mandamos a imagem para o Docker.
Nós sempre interagimos com o container e NÃO com a Imagem. A aplicação roda dentro de um container enquanto o container é criado a partir de uma imagem.
Imagem vs Container
A Imagem, como o nome sugere, é estática, enquanto o Container é dinâmico.
A Imagem especifica o que deve acontecer para que o container seja criado e o que vai ter dentro dele. Uma vez que é criada, mesmo alterando o código que a gerou, isso não à altera, os containers gerados a partir dela se mantém os mesmos sendo necessário recriar a imagem para que as atualizações sejam refletidas.
O Container é a máquina rodando criada a partir da imagem, você pode interagir com ele rodando comandos dentro dele, ou a sua aplicação pode modificar arquivos internamente.
Isso muda o container, quando você desliga ele, ao liga-lo novamente as alterações e estado se mantém enquanto a sua vida durar.
| Característica | 🖼️ Imagem | 📦 Container |
|---|---|---|
| O que é? | É a especificação usada pelo Docker para criar um container. | É a instância em execução criada a partir de uma imagem. |
| Comparação | Uma classe na programação orientada a objetos. | Um objeto criado a partir da classe. |
| Estado | Estática e imutável depois de criada. | Dinâmico, podendo sofrer alterações durante sua execução. |
| Função | Define o que o container terá e como ele será criado. | Executa a aplicação e permite interagir com o ambiente criado. |
| Origem | Pode ser criada localmente ou obtida de um repositório, como o Docker Hub. | É criado pelo Docker a partir de uma imagem. |
| Alterações | Alterar o arquivo que gerou a imagem não altera a imagem existente. É necessário reconstruí-la. | Arquivos e estado podem ser alterados durante a execução do container. |
| Execução | Não executa a aplicação diretamente. | É onde a aplicação efetivamente roda. |
| Exemplo | ubuntu:24.04, node:22, nginx:latest |
Um container criado a partir de node:22, por exemplo. |
| Relação entre eles | Serve como modelo/base. | É a instância criada a partir do modelo. |
Onde vivem as imagens remotas?
Toda vez que precisarmos de uma imagem pronta, podemos procurar por ela num registry de imagens, similar a como se encontram pacotes no npm, no packagist, no PyPI, NuGet, crates.io, etc.
O Registry do Docker é o DockerHub, que pode ser encontrado neste link: https://hub.docker.com/
É aqui que vamos encontrar as imagens base que iremos usar para construir as nossas próprias imagens.
Por exemplo, se nós quisermos fazer uma aplicação PHP, a comunidade da linguagem já mantém uma imagem oficial com ela instalada, e ferramentas que facilitam a configuração de vários aspectos da linguagem para os devs que programam em PHP por exemplo instalação das extensões ou configuração do php.ini.
Foi exatamente uma dessas imagens que nós utilizamos no exercício de Hello World da aula passada.
O comando para baixar a imagem para a sua máquina é o docker pull imagem:tag, por exemplo para baixar uma imagem do PHP na versão 8.5, poderíamos usar o seguinte comando:
docker pull php:8.5-cli-alpine
O php:8.5-cli-alpine vem direto da documentação oficial da imagem do PHP no Docker Hub
Como criar uma imagem
Para criar uma imagem, precisamos criar um arquivo chamado Dockerfile na raíz do projeto.
Dentro dele, nós escrevemos as instruções do que vão dentro do container, e o que deve acontecer durante o processo de criação.
Por exemplo, podemos começar um projeto genérico de softeware criando uma pasta chamada my-node-api, dentro dela criamos o arquivo Dockerfile, ficando assim:
my-node-api/
└── Dockerfile
Para testar vamos estruturar um projeto Node genérico:
my-node-api/
├── src/
│ └── index.js
├── package.json
├── package-lock.json
└── Dockerfile
O index.js tem só um código simples para ver se funcionou:
console.log("Hello Docker!")
O package.json contém o script dev que vamos usar no Dockerfile depois:
{
"name": "my-node-api",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "node --watch index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "MIT",
"description": ""
}
E o package-lock.json pode ser criado rodando:
npm i
Agora para definir a imagem propriamente dita, podemos escrever o seguinte no Dockerfile:
FROM node:24-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
CMD ["npm", "run", "dev"]
Vamos por partes:
FROM
O FROM diz qual a imagem nós vamos utilizar como base para criar nossa imagem, como queremos fazer um projeto Node, escolhemos a imagem chamada node:24-alpine.
É node pois é uma imagem para instalar o Node. O 24 sendo a versão instalada, e alpine sendo a variação da imagem padrão que é conhecida por ser mais leve que a imagem original.
Como saber qual imagem colocar no FROM?
Lembra do Docker Hub? É neste site que vivem as imagens que podemos referenciar no FROM.
Precisa criar um projeto na linguagem Crystal? Vá na barra de busca do Docker Hub, digite crystal que vão aparecer várias imagens criadas para a linguagem.
Normalmente, procuramos a oficial, o ideal é ler a documentação para saber como instalar e usar.
WORKDIR
O WORKDIR cria uma pasta dentro da raíz do container, e acessa ela, definindo aquela pasta como sendo onde nós vamos trabalhar a partir de agora.
Seria o equivalente de fazer o mesmo no terminal:
mkdir app
cd /app
COPY
O COPY, como o nome sugere, copia o caminho de entrada no caminho de destino. Então se escrevermos COPY package.json ./, ele copia SÓ o arquivo package.json da nossa máquina para o endereço /app/./, o mesmo que /app.
Agora se nós colocamos package*.json ./ como está no nosso Dockerfile, o asterisco (*) é entendido como incluindo tudo que vem antes dele, nesse caso: tudo que começar com package e termine com .json, logo, ele vai copiar nosso arquivo package-lock.json.
Mais para frente nós temos um outro exemplo: COPY . . Isso significa que vai copiar a pasta atual (.) para a pasta do destino a partir da workdir no container (/app). Esse comando copia tudo do nosso projeto para o projeto do Container.
RUN
O RUN roda um comando quando a imagem está sendo "buildada" (criar a imagem) e só nesse processo. Aqui ele roda direto no terminal, então qualquer comando que você executaria no seu terminal dentro da workdir do container, seria um comando válido para colocar aqui.
CMD
Já o CMD é um comando que roda quando o container é criado, então toda vez que você criar um container com essa imagem, ele vai rodar esse comando.
Qual a diferença entre RUN e CMD?
Ambos servem para rodar um comando dentro do container, só que o RUN roda apenas no build da imagem, ou seja uma vez só, já o CMD roda toda vez que o container é criado.
Juntando tudo que nós vimos até aqui, podemos analisar o Dockerfile pedaço por pedaço:
# Qual imagem do Docker Hub estamos usando de base
FROM node:24-alpine
# Cria uma pasta dentro do container e acessa ela
WORKDIR /app
# Copia todos os arquivos que
# começam com package e terminam com .json
# Ex: package.json e package-lock.json
COPY package*.json ./
# Roda o comando `npm ci` mas SÓ durante o build da imagem
RUN npm ci
# Copia Arquivos do nosso computador ---> Arquivos dentro do Docker
COPY . .
# Roda o `npm run dev` toda vez que um container for criado
CMD ["npm", "run", "dev"]
Uma vez que nós temos o Dockerfile em mãos podemos finalmente criar a imagem, e o comando para isso é bem simples:
docker build -t my-node-api .
O docker build é o comando do Docker para criar uma imagem a partir de um Dockerfile.
O -t é o parâmetro que indica o nome que queremos dar a nossa imagem.
E o . é onde está o Dockerfile por isso sempre tenha cuidado de verificar se você está mesmo na pasta do seu projeto no seu terminal antes de executar esse comando.
Podemos verificar quais imagens existem no nosso computador por meio do comando que lista todas elas:
> docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-world latest 54c9d81c0c2a 2 weeks ago 10.1kB
my-node-api latest a1b2c3d4e5f6 3 days ago 125MB
Se for necessário, podemos remover uma imagem por meio do comando:
docker rmi my-node-api
O rmi é a sigla para remove image.
Considerações finais
Com isso nós aprendemos como criar as imagens, estando um passo mais perto do tópico principal, afinal Docker é sobre containers. E como essa aula já está enorme, vamos falar mais sobre eles na semana que vem.
Fica ligado, assista a versão em vídeo no Youtube, curta, comente se tiver alguma dúvida, e compartilhe para aquele seu amigo que também está interessado no Docker.
Até a próxima.