-
Notifications
You must be signed in to change notification settings - Fork 3
Week 7 Workshop
20 March 2026 · Prof. Marc Hanheide
You will spend this session getting a real multi-container application running, reading it to understand how the pieces fit together, and then extending it. By the end you will have implemented new robot control endpoints and watched your CI pipeline test them automatically.
Go to:
https://github.com/UoL-SoCS/CMP9134_2526_template
Click Use this template (top-right) → Create a new repository → choose yourself as owner, and name your repository → choose visibility private (advised to avoid any possible collusion risk of your work, but up to you) → click Create Repository
This gives you your own repository under your GitHub account, using the template.
All your work today (and possibly your assessment work?) should live in your own repository. This is your playground, start playing...
Open VSCode and clone your repository to your machine and open it.
VS Code will detect the .devcontainer/ folder and show a notification:
"Reopen in Container"
Click it. VS Code will:
- Build the Docker images for the backend and frontend
- Pull the pre-built robot simulator image
- Start all three containers
- Install all recommended extensions inside the container
This takes 2–4 minutes the first time. Grab a coffee.
If the notification doesn't appear: open the Command Palette (
Ctrl+Shift+P/Cmd+Shift+P) and run:Dev Containers: Reopen in Container
Once the dev container is running, click the Ports tab in the VS Code bottom panel (next to Terminal):
| Label | Port | What it is |
|---|---|---|
| Frontend | 80 | The web dashboard — your main UI |
| Backend API (uvicorn) | 8000 | The FastAPI backend you will extend |
| Robot API (provided) | 5000 | The virtual robot simulator |
VS Code adds a 🌐 icon next to each forwarded port. Click it to open that service in your browser.
On GitHub Codespaces? You can also try GitHub Codespaces, which is basically VSCode in the cloud. A devcontainer allows you to define the working environment so a cloud "computer" is set up exactly with your development environments. The URLs will be something like
https://xxx-8000.app.github.devinstead oflocalhost. Use whatever URL VS Code gives you — the port numbers are the same.
Open a terminal OUTSIDE of the dev container (Terminal → New Terminal) and run:
docker ps(or simply check Docker Desktop on Windows)
You should see three services listed. Note their state — are they all running/healthy?
Now, VSCode, open each of these in your browser (use the Ports tab):
| URL | What you should see |
|---|---|
| Frontend | The basic dashboard — position, battery, status cards |
| Backend API | go to /docs, FastAPI interactive API docs (Swagger UI) |
| Robot API | go to /docs, Robot simulator API docs |
Checkpoint questions — answer these before moving on:
- How many Docker containers are running right now? Run
docker compose psto check. - Open the dashboard. Is the robot status showing as connected?
- Open the robot API docs. What endpoints does the robot expose? Does it meet your expectations.
The template is deliberately annotated — every key line in docker-compose.yml,
devcontainer.json, and the Dockerfile has a comment explaining what it does and why.
Open these files in VS Code and read through them:
docker-compose.yml.devcontainer/devcontainer.json.devcontainer/docker-compose.devcontainer.ymlbackend/Dockerfile
As you read, answer the following questions. Write your answers in a scratch file or a comment — they will be useful when writing your assessment report. You have to use a search engine to do some research yourself!
Questions on docker-compose.yml:
-
There are three services:
robot-api,backend, andfrontend. How does the backend know the URL of the robot API? (Hint: look at theenvironment:section of the backend service.) -
The backend uses
depends_onwithcondition: service_started. The frontend usescondition: service_healthy. What is the difference, and why does the frontend need the stricter condition? -
The robot API exposes port 5000 — but there is no
ports:mapping for it indocker-compose.yml. How can the backend still reach it? -
There is a commented-out
database:service. What image would it use? What would you need to uncomment to make it persist data between restarts?
Questions on the Dockerfile (backend/Dockerfile):
-
The Dockerfile has three stages:
base,development, andproduction. Which stage does the devcontainer use? Which does the CI pipeline use? (Hint: checkdocker-compose.devcontainer.yml,docker-compose.yml, and.github/workflows/ci.yml.) -
The production stage creates a non-root user called
gcs(seebackend/Dockerfile). Why is this good practice? -
The development stage defines the command to execute upon start with
CMD ["uvicorn", "main:app", ... "--reload"]. But in the dev container,uvicornis not running by default. What command overrides this, and why? (Hint:docker-compose.devcontainer.yml.)
Open backend/robot_client.py.
The get_status() method is already implemented. The move() and reset() methods
exist but raise NotImplementedError:
async def move(self, x: int, y: int) -> dict[str, Any]:
raise NotImplementedError("Move command not implemented yet")
async def reset(self) -> dict[str, Any]:
raise NotImplementedError("Reset command not implemented yet")Your job is to implement at least one of them.
Open robot-api:5000/docs (from "ports" in VSCode, this is likely http://localhost:5000/docs) in your browser.
Find the POST /api/move endpoint. It should look like this:
Click it → Try it out → fill in x and y values → Execute.
Note:
- What is the request body format? (JSON with
xandyfields) - What does a successful response look like?
- What happens if you give coordinates outside the 0–20 grid?
Do the same for POST /api/reset.
In robot_client.py, replace the NotImplementedError with a real implementation.
Model it on get_status() — it uses httpx.AsyncClient to send a GET request.
move() should send a POST request to /api/move with a JSON body:
{"x": x, "y": y}Make sure to:
- Raise
RobotConnectionErroron any exception (match the pattern inget_status()) - Return the parsed JSON response
Open backend/main.py. You can see the stub route skeletons in the comments at the bottom.
Add a new route for your new function (either move or reset):
e.g. start with
@app.post("/api/move")
async def move_robot(x: int, y: int):
"""Send a move command to the robot."""
...Hot reload: Because the dev container runs uvicorn with
--reload, your changes take effect as soon as you save the file. No restart needed.(You will need to start uvicorn first — see below if you haven't already.)
The dev container keeps the container alive but does not start uvicorn automatically (so you can make changes before it starts). In your terminal:
cd /workspace/backend
uvicorn main:app --host 0.0.0.0 --port 8000 --reloadLeave this running in the terminal. You should see:
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO: Started reloader process
Open backend API port, likely at http://localhost:8000/docs` in your browser. You should now see:
GET /api/status-
POST /api/move, or POST /api/reset
Test each one using the Try it out button in Swagger.
For /api/move, try:
- Valid coordinates:
x=5, y=3 - Out-of-range coordinates:
x=25, y=0
The robot has a chaos monkey — it randomly fails communication. Your move() or reset() implementations will sometimes fail.
Explore how to add a basic retry logic to robot_client.py.
Why does this matter? Your assessment brief explicitly requires robust error handling for the chaos monkey. The Critical Reflection section (S1, 20% of marks) expects you to discuss engineering decisions like this.
You have extended the backend. Now push your changes and watch the CI pipeline test them.
In your terminal (you can open a second terminal tab):
cd /workspace
git add backend/robot_client.py backend/main.py
git commit -m "implement move and reset robot endpoints"
git pushOr you simply use the built-in VSCode tools ;-)
Go to your forked repository on GitHub → Actions tab.
You will see a workflow called CI — Build, Test & Push running. It has two jobs:
Click into each job and read through the steps.
Questions:
- The test job runs on two Python versions simultaneously. Why?
- The compose integration test hits
http://localhost:8080/health. Which service handles that request? Trace the path. - Do your new
/api/moveand/api/resetendpoints get tested by the CI pipeline? If not, what kind of test would you write to cover them?
Ports (inside the dev container):
| Service | Port | URL |
|---|---|---|
| Frontend dashboard | 80 | http://localhost:80 |
| Your FastAPI backend | 8000 | http://localhost:8000/docs |
| Robot simulator API | 5000 | http://localhost:5000/docs |