HW01 - Design and Containerize the Edge Node
Late Policy
- Assignments submitted late will incur a deduction of 10% per day, including weekends and university holidays, from the maximum possible score. Submissions beyond three days late will result in a score of 0.
You are an edge engineer for a mid-size city’s smart-infrastructure program. The city runs unmanned pump stations across the region; each one needs a small AI-capable node that monitors the pump locally and only sends alerts and summaries to the operations center, not raw data. In this course the shared NVIDIA DGX Spark stands in for that field node: you profile and budget against real hardware instead of a spec sheet.
Before anyone commits to a node design for a pump house, you have to answer two engineering questions and prove your answers: what is this node, really, not the spec sheet but the measured device; and does the software you want to run actually fit? An edge node is a small, shared, power- and memory-constrained box. A design that does not fit the budget is not a design; it is a wish.
In this assignment you profile the shared DGX Spark, write an architecture whose resource budget provably closes, and then turn the first box of that architecture diagram into a real running container. This is the same node you build out over the rest of the course, so get the foundation right here.
Full step-by-step instructions, including every command you need to run, are in the README.md of the repository Lumen creates for you. Work from that README. This page is the summary and the requirements.
Objective and Expected Learning Outcomes
By completing this assignment, you will be able to:
- Profile a real edge node (CPU, memory, storage, accelerator, power, thermal) into a structured record.
- Map work across edge tiers and justify each placement with a real constraint (latency, privacy, bandwidth, available computing resources, offline operation).
- Build a resource budget and prove a design fits a fixed device allocation.
- Write a Dockerfile that uses layer caching and an
ENTRYPOINT/CMDsplit. - Use
compose.yamlto map ports, mount read-only and writable volumes, and enforce CPU/memory caps. - Compare a paper budget to real measured usage with
podman stats.
The Course Workflow (Lumen)
This is the first graded assignment that produces real engineering artifacts, so the workflow from HW00 still applies in full and is worth restating once.
Two pull requests travel in opposite directions, and they are easy to confuse:
| Direction | Who opens it | Who merges it | |
|---|---|---|---|
| Update PR | instructor main → your main
|
Instructor | You |
| Student PR | your development → your main
|
You | Instructor |
You merge the Update PR (rare; only when the instructor corrects a released assignment). You never merge the Student PR: opening it is your submission, and merging it is part of grading.
The Device
All work is done on the shared course DGX Spark at cs494.evl.uic.edu. If you cannot reach it, re-check the passwordless SSH setup from HW00.
Because the device is shared:
- Keep your files under your own home folder.
- Prefix any container resource you create (images, containers, compose projects) with your account name using
${USER}. - Use only your own port, which you derive from your UID in Setup. Nobody assigns it and there is no list to look up.
- You are allotted a fixed slice of the node: 4096 MB of RAM and 2 vCPUs (your instructor will confirm). Your design must fit inside that slice.
Capture the profile on the DGX Spark. The profiler runs on your laptop for development, but the profile you submit must come from the DGX Spark; grading re-runs there. A profile whose model is “unknown” and whose memory is 0 MB was captured off-device and will not be accepted.
You type podman. The node runs rootless Podman, and no Docker daemon is running on it, so a docker command in an SSH session has nothing to talk to and fails. (Inside a JupyterHub lab, docker is wired through to Podman for you; that shim does not exist in the shell.) Nothing runs as system root and no group grants it, there is no daemon, and container root is not host root: a process reporting uid=0 inside a container is mapped to your account outside it. Do not ask to be added to a docker group; on a shared node that grants root, which is exactly what the rootless setup avoids.
Instructions
Do the parts in order; the container in Step 5 reads the files produced in Steps 1 and 3.
Step 1: Profile the Node
Complete the three TODOs in the node profiler: parse MemTotal and MemAvailable from /proc/meminfo and return them in MB, detect the NVIDIA device files and CUDA install and return the required dict, and assemble the profile dict from the helper functions using the exact schema shown in the comments. Run it on the DGX Spark and confirm the output shows the real model, its core count, and non-zero memory.
Step 2: Design the Architecture and Budget
Using your measured numbers, fill in the architecture document with the tier map and placement decisions where every choice cites a real constraint, and fill in the budget file listing every Tier-2 service you plan to run with an estimated CPU and memory figure. Keep the station service; that is the container you build here.
Step 3: Prove the Budget Fits
Complete the budget checker so it sums the planned memory and CPU, compares them against your allocation, and reports headroom and whether the design fits. The check must pass. If your first draft is over budget, that is realistic: cut a service or move it to the cloud in the architecture document, adjust the budget, and re-run until it fits. Budgeting against a fixed slice rather than the device’s free memory makes your design’s correctness independent of how busy the shared device happens to be.
Step 4: Write the Dockerfile
Complete the Dockerfile so it copies the requirements file and installs it before the app code for layer caching, copies the application code, exposes port 8000, and sets an ENTRYPOINT to run the station service with a default CMD.
Step 5: Complete Compose, Run, and Verify
Complete the Compose file: the ${USER}-prefixed image name, the port mapping, the read-only profile mount and writable logs mount, the memory and CPU caps matching your allocation, and unbuffered Python output. Bring the stack up and confirm the startup log records that the profile loaded and the budget fits, then check the health and profile endpoints.
Step 6: Budget vs. Reality
With the service running, measure what it actually uses and compare it to what you budgeted. Record the budgeted and measured memory in your reflection, then bring the stack down.
Step 7 (optional): Verify GPU Access
Optional but recommended. It requires the instructor’s NVIDIA base-image tag and the DGX Spark GPU. It does not gate the core grade, but it is the container pattern HW04 depends on. Complete the GPU Dockerfile and GPU Compose file, then build and run the check both with and without GPU access to see the contrast. The lesson: the base image controls the software inside the container, and the GPU flag controls host GPU access. Both must line up for edge AI.
Step 8: Reflection
Fill out the reflection file completely with your real numbers and observations.
Submission Requirements
To receive credit, you must:
- Work on the
developmentbranch in your Lumen-provisioned repository. - Commit the completed profiler and budget checker scripts.
- Commit the node profile in both JSON and text form, captured on the DGX Spark.
- Commit the budget check output in both JSON and text form, showing that the design fits.
- Commit the filled-in architecture document and budget file.
- Commit the completed Dockerfile and Compose file.
- Commit the station startup log produced by the running container.
- Commit your completed reflection.
- Push to
developmentand open a Student PR fromdevelopmentintomain.
Do not merge the Student PR. Do not commit .venv or any virtual environment directory, and do not run a system prune or delete other students’ images on the shared device.
Evaluation Criteria
| Criterion | Weight |
|---|---|
| TODOs 1-3: profiler produces a well-formed profile from the DGX Spark (real model, non-zero memory, accelerator detected) | 20% |
Budget TODO: checkBudget.py correctly sums services and decides fits against the allocation |
15% |
architecture.md: tier map + placement decisions each justified by a real constraint |
15% |
budget.yaml: a real design that fits the allocation (checkBudget.py exits 0) |
10% |
Dockerfile builds; layer-cached install; correct ENTRYPOINT/CMD
|
15% |
compose.yaml: ${USER} image name, port map, read-only ./profile + writable ./logs, enforced caps; logs/station-startup.json produced |
15% |
Reflection with specific measured numbers (profile, budget, podman stats) |
10% |
Notes on Course-Wide Requirements
- A reflection file is required for this assignment, including the certification statement in its header.
- The workflow is unchanged from HW00:
developmentbranch, Student PR intomain, and no merging of your own PR. - This assignment builds directly on Lab 02 (System Architecture, Design and Containers). If you did that lab, you have already run every command this assignment needs; here you turn those one-off commands into graded, reusable engineering artifacts. If you are stuck on a step, read its lab part first. The repository README maps each part of the assignment to its section of the lab.
- The labs directory also ships a Podman variant of this lab. The homework targets Docker, so use the Docker version unless your instructor says otherwise.
- Your instructor verifies the shared device each semester. If a step fails in a way that looks like the device rather than your code, ask them rather than trying to debug a machine the whole class shares.
Additional Resources
-
Docker: Getting Started - Images, containers, Dockerfiles, volumes, and Compose.
-
Dockerfile reference - The authoritative reference for
FROM,COPY,RUN,EXPOSE,ENTRYPOINT, andCMD. -
Compose file reference - Services, ports, volumes, and resource limits such as
mem_limitandcpus. -
NVIDIA DGX Spark User Guide - The DGX Spark platform, DGX OS, and its CUDA stack.
-
/proc/meminfo documentation - What
MemTotalandMemAvailablemean.
