What you’re about to install
The platform is made of five services. A service is just a program that does one job and runs in the background. They talk to each other over the network.
All five run inside Docker. Docker is a tool that puts each service in its own “box” (called a container) so they can’t mess with each other and they all start the same way on every Pi. One file, docker-compose.yml, tells Docker how to run them together.
Without Docker you’d install five programs by hand, each with its own version of Python or Node, each with its own config files. Containers bundle the program and everything it needs into one box. Reset broken? Throw the box away and start a new one.
Install Docker
SSH into your Pi (you set this up in Phase 1) and run the official install script:
curl -fsSL https://get.docker.com | sudo sh
This downloads a script from Docker and runs it. It takes 3–5 minutes. When it finishes, add yourself to the docker group so you don’t have to type sudo every time:
sudo usermod -aG docker $USER
For that change to take effect, log out and log back in. (If you’re on SSH, just exit and SSH back in.) Then check Docker is alive:
docker version
docker compose version
You should see version numbers, no errors. If you see “permission denied”, you’ve forgotten the log-out / log-in.
curl … | sudo sh do?curl downloads a file from a URL. The pipe | hands it straight to sh, which runs it as a shell script with administrator privileges. You should only ever do this with scripts from trusted sources — get.docker.com is one of them.
Get the project files
The platform code lives in a Git repository. Git is a tool for tracking and sharing files. We’ll clone the repo — copy it from the internet to your Pi.
Move into the folder you set up in Phase 1, then clone:
cd /opt/iot-platform
git clone https://github.com/Computing-Hub/IoT_Master.git .
ls
If your class has its own fork (so each year-group can save changes), replace the URL above with your teacher’s fork URL. The structure is identical.
The dot at the end of git clone means “clone into the current folder”. The ls at the end shows you what arrived. You should see at least:
docker-compose.yml
.env.example
infra/
services/
frontend/
If git isn’t installed, install it:
sudo apt install -y git
Configure your .env
Some settings change between Pis — passwords, secret keys, your specific Wi-Fi name. They live in a file called .env, which is never committed to Git. There’s a template called .env.example that we copy and edit.
cd /opt/iot-platform
cp .env.example .env
nano .env
nano is a simple text editor. You should see something like this:
# MQTT credentials — change the password
MQTT_USER=platform
MQTT_PASSWORD=change-me-please
# Anthropic API key — leave blank for now (we'll add it in Phase 6)
ANTHROPIC_API_KEY=
ANTHROPIC_MODEL=claude-haiku-4-5-20251001
# Database file
DB_PATH=/data/sqlite/platform.db
For now, the only thing you must change is MQTT_PASSWORD. Pick something memorable but not obvious — not password, not your name, not 1234. Save with Ctrl+O, Enter, then exit with Ctrl+X.
.envThis file holds passwords. It’s already in .gitignore so Git ignores it — but don’t copy-paste it into Slack, email, or a screenshot. Treat it like the password to your school account.
Now create the MQTT password file Mosquitto needs:
./scripts/bootstrap-mqtt-passwd.sh
That script reads MQTT_USER and MQTT_PASSWORD from .env and writes a hashed copy to infra/mosquitto/passwd. Mosquitto will check the hash, not the plain password, when clients connect.
Start the platform
Here’s the magic command:
docker compose up -d
Reading that out loud: “Docker, compose up the services in detached mode.” -d means “run in the background” — without it, your terminal would be filled with logs from all five services at once.
The first time you run this, Docker has to download (“pull”) the images for each service. That can take 5–10 minutes on school Wi-Fi. You’ll see lines like:
[+] Pulling 47/47
✔ mosquitto Pulled 32.1s
✔ node-red Pulled 58.4s
✔ api Built 2.7s
✔ ai Built 2.4s
✔ frontend Built 4.1s
[+] Running 5/5
✔ Container iot-mosquitto Started
✔ Container iot-node-red Started
✔ Container iot-api Started
✔ Container iot-ai Started
✔ Container iot-frontend Started
Once it’s done, check what’s running:
docker compose ps
You should see five rows, each with Up in the STATUS column. If any say Restarting or Exited, check that service’s logs:
docker compose logs api # one service
docker compose logs -f # all services, live
docker compose logs --tail=50 api # last 50 lines
Press Ctrl+C to stop following logs. (That doesn’t stop the service — just stops showing you the logs.)
A small toolkit you’ll use throughout the course:
docker compose ps— what’s running.docker compose logs <service>— show logs.docker compose restart <service>— turn it off and on again.docker compose down— stop everything.docker compose up -d— start everything.
Verify each service
“Up” in Docker just means the container started. It doesn’t mean the service inside actually works. Let’s prove each one.
Find your Pi’s hostname (it should be pi-iot-XX from Phase 1):
hostname
From now on, replace pi-iot-XX in every command with the hostname your Pi reports.
5a. The Vue dashboard
On the Pi’s touchscreen, open Chromium and go to:
http://localhost/
Or from your laptop on the same Wi-Fi:
http://pi-iot-XX.local/
You should see a placeholder page that says “Pi IoT Platform”. It’s empty for now — you’ll build the real dashboard in Phase 5.
5b. Node-RED
http://pi-iot-XX.local:1880/
Node-RED’s flow editor should load — a blank canvas with a sidebar of “nodes”. We’ll use this in Phase 3.
5c. The API
curl http://localhost:8000/health
Should print:
{"status":"ok"}
5d. The AI service
curl http://localhost:8001/health
Same response: {"status":"ok"}. (No API key yet, that’s fine — the service is alive.)
/health endpoint?A tiny URL that every service offers, just to say “I’m alive”. We’ll use these in monitoring and in Docker’s health checks. They’re a habit you should pick up — every service you ever build should have one.
Round-trip an MQTT message
This is the showpiece of Phase 2. You’ll send a message from one laptop, through the Pi’s MQTT broker, to a different laptop — without writing any code.
On both laptops, install the MQTT command-line tools (one-off, ~30 seconds each):
sudo apt install -y mosquitto-clients # Debian/Ubuntu/Raspberry Pi OS
brew install mosquitto # macOS
On laptop A, listen for messages on a test topic:
mosquitto_sub -h pi-iot-XX.local -p 1883 \
-u platform -P "<your MQTT password>" \
-t "dev/hello"
The \ at the end of a line means “the command continues on the next line”. After running it, the terminal will sit there and wait. That’s correct — it’s listening.
On laptop B, send a message to the same topic:
mosquitto_pub -h pi-iot-XX.local -p 1883 \
-u platform -P "<your MQTT password>" \
-t "dev/hello" \
-m "Hello from laptop B!"
Watch laptop A. The message should appear instantly:
Hello from laptop B!
Press Ctrl+C on laptop A to stop listening. Now swap roles — laptop A sends, laptop B listens. Try a different topic, like dev/temperature, and a different message.
Laptop B sent a message to the broker on the Pi. The broker saw that laptop A had subscribed to that topic, so it forwarded the message. Neither laptop knew about the other — they only knew about the broker. That’s how every IoT device on the platform will work.
If mosquitto_sub says “Connection Refused: not authorised”, the password is wrong. Check the password is the one in .env on the Pi. If it says “No route to host”, your laptop can’t reach the Pi — check it’s on the same Wi-Fi.
Final verification
Tick everything below before declaring Phase 2 finished. Phase 3 assumes the bus is working and the API is reachable.
Phase 2 sign-off
Reboot the Pi (sudo reboot). After it comes back, SSH in and run docker compose ps — everything should be Up again. If not, something’s missing in the compose file’s restart policy.