Aula de Laboratório — Primeiros passos com Docker (stack IoT)
Ferramentas: Docker · VS Code (extensão Container Tools) · Mosquitto · Node-RED · MQTT Explorer
1. Objetivos de aprendizagem
Ao final desta aula, o aluno será capaz de:
- Explicar a diferença entre imagem e container e o papel do
docker compose. - Subir e gerenciar uma stack de vários serviços com um único arquivo.
- Usar o VS Code (extensão Container Tools) para subir, inspecionar e depurar containers.
- Entender portas, volumes e redes na prática.
- Verificar o broker MQTT (Mosquitto) e o MQTT Explorer, e abrir o Node-RED.
Os cartões "Ponto de commit" marcam cada tarefa avaliada. Copie o comando do cartão e faça o commit com a mensagem sugerida. Algumas tarefas pedem uma evidência (a saída de um comando salva num arquivo) — isso também é avaliado.
2. Pré-requisitos e instalação
- Docker instalado (Docker Desktop no Windows/macOS; Docker Engine no Linux).
- VS Code com a extensão Container Tools (por Microsoft): abra a aba de extensões e procure por
container tools.A antiga extensão "Docker" foi substituída pela Container Tools (
ms-azuretools.vscode-containers), que traz o Container Explorer, IntelliSense dedocker-compose.ymle comandos de Compose. - Confirme no terminal integrado do VS Code:
docker --versiondocker compose version
3. Conceitos rápidos
| Termo | O que é |
|---|---|
| Imagem | Um "molde" somente-leitura com o app e suas dependências. |
| Container | Uma instância em execução de uma imagem. |
| Dockerfile | A "receita" para construir uma imagem. |
docker compose | Orquestra vários containers a partir de um docker-compose.yml. |
Porta host:container | Publica uma porta do container na sua máquina. |
| Volume | Guarda dados fora do container (persistem após reiniciar). |
| Rede (network) | Deixa os containers se acharem pelo nome do serviço. |
4. A stack esp32-iot
Três serviços trabalhando juntos:
| Serviço | Host | Container | Para quê |
|---|---|---|---|
| Mosquitto | 1883 | 1883 | Broker MQTT (o ESP32 conecta aqui) |
| Mosquitto | 9001 | 9001 | MQTT sobre WebSockets (navegador) |
| Node-RED | 1880 | 1880 | Editor de fluxos (lógica low-code) |
| MQTT Explorer | 1881 | 3000 | Ver e publicar mensagens MQTT |
- docker-compose.yml
- Dockerfile (Node-RED)
name: esp32-iot
services:
mosquitto:
image: eclipse-mosquitto:latest
container_name: mosquitto
ports:
- "1883:1883" # MQTT
- "9001:9001" # WebSockets
volumes:
- ./mosquitto/config:/mosquitto/config
- ./mosquitto/data:/mosquitto/data
- ./mosquitto/log:/mosquitto/log
networks:
- iot-network
restart: unless-stopped
extra_hosts:
- "host.docker.internal:host-gateway"
node-red:
build:
context: .
dockerfile: Dockerfile
container_name: node-red
environment:
- TZ=America/Sao_Paulo
ports:
- "1880:1880" # UI do Node-RED
volumes:
- node-red-data:/data
depends_on:
- mosquitto
networks:
- iot-network
restart: unless-stopped
mqtt-explorer:
image: ruseler/mqtt-explorer:local
container_name: mqtt-explorer
restart: unless-stopped
ports:
- "1881:3000"
environment:
MQTT_EXPLORER_SKIP_AUTH: "true"
networks:
- iot-network
volumes:
- ./mqtt-explorer/data:/app/data
depends_on:
- mosquitto
volumes:
node-red-data:
networks:
iot-network:
driver: bridge
FROM nodered/node-red:latest
USER root
RUN mkdir -p /data/sqlite && chown -R node-red:node-red /data/sqlite
USER node-red
WORKDIR /usr/src/node-red
RUN npm install --unsafe-perm --no-update-notifier --no-fund \
node-red-node-sqlite \
@flowfuse/node-red-dashboard \
node-red-contrib-bcrypt \
node-red-contrib-finite-statemachine \
@flowfuse/node-red-dashboard-2-ui-led \
node-red-node-serialport \
node-red-node-ui-table \
&& npm cache clean --force
COPY settings.js /data/settings.js
COPY flows.json /data/flows.json
EXPOSE 1880
O Node-RED é construído a partir desta imagem, já com nós extras (dashboard, sqlite, etc.).
- O
DockerfilefazCOPY settings.jseCOPY flows.json— esses arquivos precisam existir na pasta (mesmo mínimos), ou obuildfalha. Vamos criá-los na Parte 1. - A imagem
ruseler/mqtt-explorer:localpode não existir na sua máquina. Se opullfalhar, troque:localpor:latest. - O comentário original do Node-RED citava "1881", mas o mapeamento é
1880:1880(host 1880). O MQTT Explorer é quem usa o 1881. Deixe assim (sem conflito) — ou veja o Desafio D1.
Parte 1 — Preparar o projeto no VS Code
Abra a pasta do projeto no VS Code (File → Open Folder) e monte esta estrutura:
docker-iot-stack/
├── docker-compose.yml
├── Dockerfile
├── settings.js
├── flows.json
├── mosquitto/
│ ├── config/
│ ├── data/
│ └── log/
└── mqtt-explorer/
└── data/
Crie os dois arquivos que o Dockerfile copia (senão o build quebra):
- flows.json
- settings.js
[]
Um array vazio: o Node-RED sobe sem fluxos (vamos criar depois).
// settings.js minimo — o Dockerfile faz COPY deste arquivo.
// O Node-RED completa o resto com os valores padrao.
module.exports = {
flowFile: "flows.json",
uiPort: process.env.PORT || 1880,
// Laboratorio: sem autenticacao. NAO use assim em producao.
};
prepara a stack: docker-compose, Dockerfile, settings.js e flows.json
git add . && git commit -m "T1: prepara a stack: docker-compose, Dockerfile, settings.js e flows.json" && git push
Parte 2 — Configurar o broker (Mosquitto)
Crie mosquitto/config/mosquitto.conf. Sem isso, o Mosquitto 2.x sobe fechado (recusa conexões externas e anônimas).
listener 1883 0.0.0.0
allow_anonymous true
persistence true
persistence_location /mosquitto/data/
log_dest stdout
log_dest file /mosquitto/log/mosquitto.log
listener 9001
protocol websockets
listener 1883 0.0.0.0 faz o broker aceitar conexões de outras máquinas (o ESP32). allow_anonymous true dispensa usuário/senha — o padrão do Mosquitto 2.x é recusar.
configura o broker com mosquitto.conf
git add . && git commit -m "T2: configura o broker com mosquitto.conf" && git push
Parte 3 — Subir a stack pelo VS Code
Você pode subir de duas formas (use a que preferir):
- Pela extensão (interface)
- Pelo terminal
- Clique com o botão direito no
docker-compose.yml→ Compose Up. - Abra o Container Explorer (ícone da baleia/containers na barra lateral) e veja os três containers subindo:
mosquitto,node-red,mqtt-explorer.
No terminal integrado (Terminal → New Terminal):
docker compose up -d --build # --build porque o Node-RED e construido do Dockerfile
docker compose ps # lista os servicos e o estado
Registre a evidência: salve a saída de docker compose ps em evidencias/compose-ps.txt.
mkdir -p evidencias
docker compose ps > evidencias/compose-ps.txt
Você deve ver os três serviços com estado running/Up.
sobe a stack e registra o docker compose ps
git add docker-compose.yml evidencias/compose-ps.txt && git commit -m "T3: sobe a stack e registra o docker compose ps" && git push
Parte 4 — Explorar e testar no VS Code
Explorar: no Container Explorer, clique com o botão direito em um container para View Logs (ver o log em tempo real) ou Attach Shell (abrir um terminal dentro do container).
Testar o MQTT direto no broker, via attach shell no mosquitto (ou docker exec):
# terminal 1 — assinar tudo em elt85b/
docker exec -it mosquitto mosquitto_sub -t "elt85b/#" -v
# terminal 2 — publicar uma mensagem
docker exec -it mosquitto mosquitto_pub -t "elt85b/teste" -m "ola docker"
Abra também o MQTT Explorer em http://localhost:1881 e veja a mensagem aparecer na árvore de tópicos.
Registre a evidência do teste:
docker exec mosquitto mosquitto_sub -t "elt85b/#" -v -C 1 > evidencias/mqtt.txt &
docker exec mosquitto mosquitto_pub -t "elt85b/teste" -m "ola docker"
testa o MQTT (publica e assina) e registra evidencia
git add evidencias/mqtt.txt && git commit -m "T4: testa o MQTT (publica e assina) e registra evidencia" && git push
Parte 5 — Primeiro fluxo no Node-RED
- Abra
http://localhost:1880no navegador. - Arraste um nó inject e um nó debug para a tela e ligue um ao outro.
- Clique em Deploy e depois no botão do inject — veja a mensagem no painel debug.
- Exporte o fluxo: menu (☰) → Export → Download e salve como
flows.jsonna pasta do projeto (substituindo o vazio).
O flows.json é o seu fluxo em texto. Versioná-lo no Git permite avaliar e recriar o fluxo em qualquer máquina.
cria um fluxo no Node-RED e exporta flows.json
git add flows.json && git commit -m "T5: cria um fluxo no Node-RED e exporta flows.json" && git push
6. Projeto integrador — Dashboard da telemetria
Junta a stack ao mundo real: um fluxo que assina a telemetria publicada por um ESP32 (do lab de WiFi/MQTT) e mostra o valor.
- Adicione um nó mqtt in conectado ao broker
mosquitto:1883(dentro da rede Docker, o host é o nome do serviço:mosquitto), assinandoelt85b/grupo-a/telemetria. - Ligue-o a um nó debug e a um nó de dashboard (ex.: text ou chart do
@flowfuse/node-red-dashboard). - Deploy. Publique uma mensagem de teste no tópico (pelo MQTT Explorer) e veja aparecer.
- Exporte o fluxo para
flows.json.
Como o Node-RED e o Mosquitto estão na mesma rede (iot-network), o Node-RED alcança o broker pelo nome mosquitto na porta 1883 — não use localhost aqui (dentro do container, localhost é o próprio Node-RED).
projeto: fluxo Node-RED assinando a telemetria
git add flows.json && git commit -m "T6: projeto: fluxo Node-RED assinando a telemetria" && git push
7. Desafios
Tente resolver sem olhar o gabarito. Faça o commit de cada desafio conforme concluir.
D1. O comentário original mapeava o Node-RED em 1881, que conflita com o MQTT Explorer. Mude o Node-RED para uma porta livre no host (ex.: 1882:1880) e explique por que 1881:1880 daria erro.
desafio D1: ajusta portas sem conflito
git add docker-compose.yml && git commit -m "T7: desafio D1: ajusta portas sem conflito" && git push
D2. Garanta que as mensagens retidas e a fila do broker sobrevivam a um docker compose down + up. (Dica: o volume ./mosquitto/data e persistence true já ajudam — teste publicando com -r e reiniciando.)
desafio D2: persiste dados do broker em volume
git add evidencias/persistencia.txt && git commit -m "T8: desafio D2: persiste dados do broker em volume" && git push
D3. No Node-RED, adicione um dashboard (gauge ou chart) que mostre o campo temp do JSON recebido. Exporte o fluxo.
desafio D3: dashboard no Node-RED com o valor recebido
git add flows.json && git commit -m "T9: desafio D3: dashboard no Node-RED com o valor recebido" && git push
D4. Adicione um healthcheck do Mosquitto no docker-compose.yml (ex.: testar a porta 1883) para o Compose saber se o broker está saudável.
desafio D4: healthcheck do mosquitto no compose
git add docker-compose.yml && git commit -m "T10: desafio D4: healthcheck do mosquitto no compose" && git push
8. Gabarito
Só abra depois de tentar os desafios por conta própria.
Ver soluções
D1. 1881 já é o host do MQTT Explorer; duas publicações do mesmo host geram erro "port is already allocated". Use outra porta livre:
node-red:
ports:
- "1882:1880" # host 1882 -> container 1880
D2. Publique uma mensagem retida e reinicie:
docker exec mosquitto mosquitto_pub -t "elt85b/status" -m "online" -r
docker compose down && docker compose up -d
docker exec mosquitto mosquitto_sub -t "elt85b/status" -v -C 1 > evidencias/persistencia.txt
A mensagem retida reaparece porque persistence true grava em ./mosquitto/data (volume).
D3. Fluxo: mqtt in → json (converte texto em objeto) → change/function (extrai msg.payload.temp) → nó de gauge/chart do dashboard. Exporte para flows.json.
D4. Healthcheck no serviço mosquitto:
healthcheck:
test:
["CMD-SHELL", "mosquitto_sub -t '$$SYS/#' -C 1 -W 3 -h localhost || exit 1"]
interval: 30s
timeout: 5s
retries: 3
9. Comandos úteis (cola)
docker compose up -d --build # sobe (rebuild do Node-RED)
docker compose ps # estado dos servicos
docker compose logs -f node-red # acompanha o log de um servico
docker compose restart mosquitto # reinicia so o broker
docker compose down # derruba (mantem volumes)
docker compose down -v # derruba e apaga volumes nomeados
10. Checklist de encerramento
- Instalei o Docker e a extensão Container Tools no VS Code.
- Subi a stack (
up -d --build) e vi os 3 serviços em running. - Testei o MQTT (publiquei e assinei) e vi no MQTT Explorer (
localhost:1881). - Abri o Node-RED (
localhost:1880) e criei um fluxo. - Entendi por que o Node-RED usa o host
mosquitto(rede Docker) e nãolocalhost. - Resolvi ao menos os desafios D1 a D4.
- Fiz o commit de cada ponto marcado (T1 a T10).
Fim da aula.