497 lines
16 KiB
Markdown
497 lines
16 KiB
Markdown
# Docker — quick reference
|
|
|
|
Full reference: [`docs/20_docker_image.md`](../docs/20_docker_image.md).
|
|
This file is just the copy-paste commands.
|
|
|
|
| File | What it is |
|
|
|---|---|
|
|
| `Dockerfile` | Multi-stage image. Mock-only by default; hardware variant via `--build-arg INCLUDE_MELEXIS=1` + a BuildKit secret carrying the Melexis Python packages. |
|
|
| `compose.hw.yml` | docker-compose service for the hardware variant — host networking, USB device passthrough, reports volume, bench-config bind mount. |
|
|
| `../.dockerignore` | Excludes `.venv/`, `reports/*`, the deprecated BabyLIN SDK, generated caches, etc. |
|
|
|
|
All commands below assume you're running them **from the repo root**
|
|
inside a WSL2 distro (Ubuntu / Debian / …) with `docker` already on
|
|
the `$PATH`. If `docker --version` doesn't work yet, install it first
|
|
— see [§ Prerequisites](#prerequisites--install-docker-on-wsl).
|
|
|
|
---
|
|
|
|
## Prerequisites — install Docker on WSL
|
|
|
|
If `docker --version` already prints a version, skip this section.
|
|
|
|
Two install paths. **Option A** (Docker Desktop) is the easy one
|
|
that most teams use. **Option B** (Docker Engine directly inside
|
|
WSL2) is for environments where Docker Desktop's licensing or
|
|
policies are blocked.
|
|
|
|
### Option A — Docker Desktop on Windows (WSL2 backend, recommended)
|
|
|
|
The Windows host runs Docker Desktop; your WSL2 distro talks to it.
|
|
You install the daemon in exactly one place (Windows) and it appears
|
|
seamlessly inside every enabled WSL distro.
|
|
|
|
1. **Make sure WSL2 is current** (Windows PowerShell, admin):
|
|
```powershell
|
|
wsl --install # no-op if WSL is already there
|
|
wsl --set-default-version 2
|
|
wsl --update
|
|
```
|
|
Reboot if Windows prompts.
|
|
|
|
2. **Verify your distro is on WSL 2** (not WSL 1):
|
|
```powershell
|
|
wsl -l -v
|
|
```
|
|
The `VERSION` column should read `2` for your distro. If it shows
|
|
`1`, convert: `wsl --set-version <DistroName> 2`.
|
|
|
|
3. **Install Docker Desktop**:
|
|
- Download <https://www.docker.com/products/docker-desktop/>.
|
|
- During install, leave **"Use WSL 2 instead of Hyper-V"** ticked.
|
|
- Launch Docker Desktop after install completes.
|
|
|
|
4. **Enable WSL integration** (one-time):
|
|
- Docker Desktop → **Settings** → **Resources** → **WSL Integration**.
|
|
- Toggle on integration for every WSL distro you'll run `docker`
|
|
from (Ubuntu, Debian, …).
|
|
- Click **Apply & Restart**.
|
|
|
|
5. **Verify from inside WSL**:
|
|
```bash
|
|
docker --version
|
|
docker run --rm hello-world
|
|
```
|
|
`hello-world` should print "Hello from Docker!" and exit 0.
|
|
|
|
### Option B — Docker Engine inside WSL2 (no Docker Desktop)
|
|
|
|
Use this when Docker Desktop isn't allowed (corporate / license
|
|
policy) or when you want a single isolated Linux install.
|
|
|
|
1. **Enable `systemd` in WSL2** (Docker's daemon expects it). In
|
|
your WSL distro edit `/etc/wsl.conf`:
|
|
```ini
|
|
[boot]
|
|
systemd=true
|
|
```
|
|
Then from Windows PowerShell:
|
|
```powershell
|
|
wsl --shutdown
|
|
```
|
|
Reopen the WSL terminal; check `systemctl --version` runs.
|
|
|
|
2. **Install Docker Engine** (Ubuntu / Debian example — Docker's
|
|
official apt repo):
|
|
```bash
|
|
# Remove anything old that might shadow the new install
|
|
sudo apt-get remove -y docker docker-engine docker.io containerd runc 2>/dev/null || true
|
|
|
|
# Add Docker's apt key + repo
|
|
sudo apt-get update
|
|
sudo apt-get install -y ca-certificates curl gnupg lsb-release
|
|
sudo install -m 0755 -d /etc/apt/keyrings
|
|
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
|
|
| sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
|
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
|
|
https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" \
|
|
| sudo tee /etc/apt/sources.list.d/docker.list >/dev/null
|
|
|
|
# Install
|
|
sudo apt-get update
|
|
sudo apt-get install -y docker-ce docker-ce-cli containerd.io \
|
|
docker-buildx-plugin docker-compose-plugin
|
|
```
|
|
|
|
3. **Run without `sudo`**:
|
|
```bash
|
|
sudo usermod -aG docker $USER
|
|
```
|
|
Log out and back into WSL (or `exec su -l $USER`) so the new group
|
|
membership takes effect.
|
|
|
|
4. **Start the daemon**:
|
|
```bash
|
|
sudo systemctl enable --now docker
|
|
```
|
|
|
|
5. **Verify**:
|
|
```bash
|
|
docker --version
|
|
docker run --rm hello-world
|
|
```
|
|
|
|
### Hardware-only — pass the Owon PSU USB device into WSL with `usbipd-win`
|
|
|
|
The mock image needs nothing beyond Docker itself. The hardware
|
|
image needs the Owon PSU's USB-serial adapter exposed inside WSL2.
|
|
Windows doesn't share USB devices with WSL2 out of the box; the
|
|
de-facto bridge is [`usbipd-win`](https://github.com/dorssel/usbipd-win).
|
|
|
|
1. **Install `usbipd-win` on Windows** (PowerShell, admin):
|
|
```powershell
|
|
winget install --interactive --exact dorssel.usbipd-win
|
|
```
|
|
Reboot.
|
|
|
|
2. **List USB devices** to find the BUSID of the serial adapter:
|
|
```powershell
|
|
usbipd list
|
|
```
|
|
Look for a row that describes your adapter — "USB Serial",
|
|
"CH340", "FT232", "Owon" — and note its `BUSID` (e.g. `2-3`).
|
|
|
|
3. **Bind the device** (one-time per device, admin):
|
|
```powershell
|
|
usbipd bind --busid 2-3
|
|
```
|
|
|
|
4. **Attach the device to WSL** (every time you plug it in, normal
|
|
user):
|
|
```powershell
|
|
usbipd attach --wsl --busid 2-3
|
|
```
|
|
|
|
5. **Confirm it appeared inside WSL**:
|
|
```bash
|
|
ls /dev/ttyUSB*
|
|
```
|
|
You should see `/dev/ttyUSB0` (or similar). That's the path you
|
|
pass to `docker run --device /dev/ttyUSB0:/dev/ttyUSB0`.
|
|
|
|
If you want the device to re-attach automatically every time you
|
|
plug it in, use `usbipd attach --auto-attach --wsl --busid 2-3`
|
|
(consult `usbipd --help` for the full set of options).
|
|
|
|
### MUM network access (192.168.7.2)
|
|
|
|
The MUM presents itself as a **USB-RNDIS Ethernet adapter** on
|
|
Windows. With Docker Desktop's WSL2 backend, `--network host` in
|
|
the container reaches the MUM automatically — no extra setup beyond
|
|
plugging the MUM in and seeing it appear in `ipconfig` (it should
|
|
add an interface with a 192.168.7.x address on the Windows side).
|
|
|
|
If you went with Option B (Engine in WSL2), the MUM still works
|
|
because the WSL2 distro shares the Windows network stack for
|
|
host-mode containers.
|
|
|
|
### Sanity check before the hardware run
|
|
|
|
```bash
|
|
# Docker reachable from WSL?
|
|
docker version
|
|
|
|
# USB-serial visible in WSL?
|
|
ls -la /dev/ttyUSB*
|
|
|
|
# MUM reachable?
|
|
ping -c 2 192.168.7.2
|
|
```
|
|
|
|
If all three succeed you're ready for the hardware run below.
|
|
|
|
---
|
|
|
|
## Mock-only image (CI-ready, no hardware needed)
|
|
|
|
### Build
|
|
|
|
```bash
|
|
docker build -f docker/Dockerfile -t ecu-tests:mock .
|
|
```
|
|
|
|
### Run the mock suite
|
|
|
|
```bash
|
|
mkdir -p reports
|
|
|
|
docker run --rm \
|
|
-v "$PWD/reports:/reports" \
|
|
ecu-tests:mock \
|
|
pytest -m "not hardware" -v \
|
|
--junitxml=/reports/junit.xml \
|
|
--html=/reports/report.html --self-contained-html
|
|
```
|
|
|
|
When the container exits, `reports/report.html` and
|
|
`reports/junit.xml` are on the host. Open the HTML report:
|
|
|
|
```bash
|
|
xdg-open reports/report.html # Linux
|
|
open reports/report.html # macOS
|
|
start reports\report.html # Windows
|
|
```
|
|
|
|
### Interactive shell
|
|
|
|
```bash
|
|
docker run --rm -it -v "$PWD:/workspace" ecu-tests:mock bash
|
|
```
|
|
|
|
Edit files on the host, run `pytest` inside the container — code
|
|
changes show up immediately.
|
|
|
|
---
|
|
|
|
## Hardware image (real bench)
|
|
|
|
### One-time setup — Melexis packages
|
|
|
|
`pylin` / `pymumclient` / `pylinframe` ship inside the Melexis IDE,
|
|
not on PyPI. Bundle them into a tarball that you'll pass as a
|
|
BuildKit secret:
|
|
|
|
```bash
|
|
# Adjust the path to where Melexis IDE is installed
|
|
MELEXIS_SITE="/mnt/c/Program Files/Melexis/Melexis IDE/plugins/com.melexis.mlxide.python_1.2.0.202408130945/python/Lib/site-packages"
|
|
|
|
tar -czf melexis-pkgs.tar.gz \
|
|
-C "$MELEXIS_SITE" \
|
|
pylin pymumclient pylinframe
|
|
```
|
|
|
|
The tarball is gitignored (see `.dockerignore`) and never enters
|
|
any image layer — BuildKit's `--mount=type=secret` only exposes it
|
|
to the single `RUN` step that copies the packages into
|
|
`/opt/venv/lib/python3.x/site-packages/`.
|
|
|
|
> **License**: the resulting image contains proprietary Melexis
|
|
> code. Treat it like the Melexis IDE itself — keep it on a private
|
|
> registry, not Docker Hub.
|
|
|
|
### Build
|
|
|
|
```bash
|
|
DOCKER_BUILDKIT=1 docker build \
|
|
-f docker/Dockerfile -t ecu-tests:hw \
|
|
--build-arg INCLUDE_MELEXIS=1 \
|
|
--secret id=melexis_tarball,src=./melexis-pkgs.tar.gz \
|
|
.
|
|
```
|
|
|
|
Verify the Melexis packages landed inside the image:
|
|
|
|
```bash
|
|
docker run --rm ecu-tests:hw \
|
|
python -c "import pylin, pymumclient, pylinframe; print('OK')"
|
|
```
|
|
|
|
### Run the hardware suite — direct `docker run`
|
|
|
|
```bash
|
|
docker run --rm \
|
|
--network host \
|
|
--device /dev/ttyUSB0:/dev/ttyUSB0 \
|
|
--group-add dialout \
|
|
-v "$PWD/reports:/reports" \
|
|
-v "$PWD/config/test_config.yaml:/workspace/config/test_config.yaml:ro" \
|
|
-e ECU_TESTS_CONFIG=/workspace/config/test_config.yaml \
|
|
ecu-tests:hw \
|
|
pytest -m "hardware and mum and not slow" -v \
|
|
--junitxml=/reports/junit.xml \
|
|
--html=/reports/report.html --self-contained-html
|
|
```
|
|
|
|
The flags, in plain English:
|
|
|
|
| Flag | Reason |
|
|
|---|---|
|
|
| `--network host` | MUM is at `192.168.7.2` via USB-RNDIS on the host; bridge networking would hide it. |
|
|
| `--device /dev/ttyUSB0:/dev/ttyUSB0` | Pass the Owon PSU's USB-serial device into the container. Adjust to whatever `ls /dev/ttyUSB*` shows on the host. |
|
|
| `--group-add dialout` | Without it, the `tester` user can't open the serial device. |
|
|
| `-v config/test_config.yaml:…:ro` | Tweak bench config without rebuilding the image. |
|
|
|
|
### Run via docker-compose
|
|
|
|
```bash
|
|
docker compose -f docker/compose.hw.yml build
|
|
docker compose -f docker/compose.hw.yml up --abort-on-container-exit
|
|
```
|
|
|
|
Same effect as the `docker run` above, but the parameters are
|
|
checked into `compose.hw.yml` so all you remember is the file path.
|
|
|
|
### Iteration — edit-on-host, run-in-container
|
|
|
|
```bash
|
|
docker run --rm -it \
|
|
--network host \
|
|
--device /dev/ttyUSB0:/dev/ttyUSB0 \
|
|
--group-add dialout \
|
|
-v "$PWD:/workspace" \
|
|
-v "$PWD/reports:/reports" \
|
|
ecu-tests:hw \
|
|
bash
|
|
```
|
|
|
|
Inside the container:
|
|
|
|
```bash
|
|
# Run a specific file
|
|
pytest tests/hardware/test_overvolt.py -v -s
|
|
|
|
# Or one parametrized case
|
|
pytest "tests/hardware/test_overvolt.py::test_template_voltage_status_parametrized[overvoltage]" -v -s
|
|
|
|
# Or the settle characterization
|
|
pytest -m psu_settling -v -s
|
|
```
|
|
|
|
---
|
|
|
|
## Where everything lives
|
|
|
|
After `docker build` and `docker run`, three different stores hold
|
|
three different things. Knowing the difference saves time when you
|
|
want to find a report, free disk space, or confirm "did my build
|
|
actually succeed?"
|
|
|
|
| Thing | Lives where | How you access it |
|
|
|---|---|---|
|
|
| The **image** | Docker daemon's content-addressed layer store. Not a single file. | `docker images`, `docker inspect`, `docker history` |
|
|
| A **running / stopped container** | Daemon's runtime state. Ephemeral when `--rm` is used. | `docker ps`, `docker ps -a`, `docker logs`, `docker exec` |
|
|
| The **test reports** | Host filesystem at `./reports/`, via the `-v` bind-mount in every run command. Survives container deletion. | `ls reports/`, open `reports/report.html` |
|
|
|
|
### The image
|
|
|
|
You **don't** navigate to it as files — query it through `docker`:
|
|
|
|
```bash
|
|
docker images # all images on this daemon
|
|
docker images ecu-tests # just the ones tagged ecu-tests
|
|
docker inspect ecu-tests:mock # full metadata (JSON)
|
|
docker history ecu-tests:mock # layer-by-layer breakdown
|
|
```
|
|
|
|
The on-disk location is daemon-internal:
|
|
|
|
| Host setup | Backing store |
|
|
|---|---|
|
|
| Native Docker Engine on Linux (Option B in the install section) | `/var/lib/docker/overlay2/…` |
|
|
| Docker Desktop + WSL2 (Option A) | Inside a hidden WSL2 distro `docker-desktop-data`. Windows side: `%LOCALAPPDATA%\Docker\wsl\disk\docker_data.vhdx`. **Don't poke directly** — always use the `docker` CLI. |
|
|
|
|
Images persist across reboots until you delete them:
|
|
|
|
```bash
|
|
docker rmi ecu-tests:mock # one image
|
|
docker system prune -a # everything unused (careful)
|
|
docker system df # what's eating disk
|
|
```
|
|
|
|
### A running container
|
|
|
|
`docker run …` creates a container from the image. The container has
|
|
its own writable filesystem layer on top of the image's read-only
|
|
layers. The image is unchanged when the container exits.
|
|
|
|
```bash
|
|
docker ps # running right now
|
|
docker ps -a # all, including exited
|
|
docker logs <id> # captured stdout / stderr
|
|
docker exec -it <id> bash # shell into a still-running container
|
|
```
|
|
|
|
Every run command in this README uses `--rm`, so the container is
|
|
deleted the moment it exits. The **image** stays. The **reports**
|
|
(see below) stay too because they're on the host filesystem, not
|
|
inside the container.
|
|
|
|
### Inside the container — what the Dockerfile lays out
|
|
|
|
```
|
|
/ (container root)
|
|
├── opt/
|
|
│ └── venv/ ← Python venv with all pip-installed deps
|
|
├── workspace/ ← the repo, copied in at build time
|
|
│ ├── ecu_framework/
|
|
│ ├── tests/
|
|
│ ├── config/
|
|
│ └── …
|
|
├── reports/ ← mount point for the host's ./reports/
|
|
└── home/tester/ ← unprivileged user home (uid 1000)
|
|
```
|
|
|
|
Peek at the layout from a throwaway container:
|
|
|
|
```bash
|
|
docker run --rm -it ecu-tests:mock bash
|
|
# inside:
|
|
ls /workspace
|
|
ls /opt/venv/bin
|
|
which pytest
|
|
```
|
|
|
|
`/workspace` is a **frozen snapshot of the repo from the moment you
|
|
ran `docker build`**. Edits to files on the host afterwards do NOT
|
|
show up inside the image — unless you bind-mount the repo at run
|
|
time:
|
|
|
|
```bash
|
|
docker run --rm -it -v "$PWD:/workspace" ecu-tests:mock bash
|
|
```
|
|
|
|
(That's exactly what the "Iteration" example does.)
|
|
|
|
### Reports on the host — what you actually look at
|
|
|
|
Every `docker run` command in this README includes a bind-mount:
|
|
|
|
```
|
|
-v "$PWD/reports:/reports"
|
|
```
|
|
|
|
The container writes its outputs to `/reports/`; the daemon's
|
|
bind-mount makes those writes show up on the host at `./reports/`
|
|
in your repo. After the container exits, the files are still there:
|
|
|
|
```
|
|
<repo root>/
|
|
└── reports/
|
|
├── report.html ← open this in a browser
|
|
├── junit.xml ← machine-readable for CI
|
|
├── summary.md
|
|
└── requirements_coverage.json
|
|
```
|
|
|
|
`--rm` deletes the container; it does **not** touch the bind-mounted
|
|
host directory.
|
|
|
|
### Three commands cover 95% of "where is it?"
|
|
|
|
```bash
|
|
docker images ecu-tests # is the image there?
|
|
docker run --rm -v "$PWD/reports:/reports" \
|
|
ecu-tests:mock pytest -m "not hardware" -q
|
|
ls reports/ # outputs landed where?
|
|
```
|
|
|
|
---
|
|
|
|
## Platform notes
|
|
|
|
- **Linux**: works as shown above.
|
|
- **WSL2 (Windows)**: USB devices need `usbipd-win` to bind them
|
|
into the WSL2 distro; from there they appear as `/dev/ttyUSB0`
|
|
exactly like on native Linux. Docker Desktop bridges WSL2 to the
|
|
host network, so `--network host` reaches the MUM normally.
|
|
- **macOS Docker Desktop**: USB passthrough is **not** supported.
|
|
Workaround is to run a TCP-to-serial bridge on the host
|
|
(`socat`) and have the container connect to that — fiddly,
|
|
documented in `docs/20_docker_image.md` §4.3 as a non-default
|
|
path.
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
| Symptom | Likely cause | Fix |
|
|
|---|---|---|
|
|
| `ModuleNotFoundError: No module named 'pylin'` | Image built without `INCLUDE_MELEXIS=1` | Rebuild with the build-arg + secret |
|
|
| `Permission denied: '/dev/ttyUSB0'` | Missing `--group-add dialout` | Add it (or the group that owns the device on the host) |
|
|
| MUM unreachable at 192.168.7.2 | Bridge network instead of host network | Add `--network host` (Linux); on macOS see §4.3 |
|
|
| Empty `reports/` after run | `/reports` not bind-mounted | Add `-v "$PWD/reports:/reports"` |
|
|
| HTML report missing styling | Forgot `--self-contained-html` | Pytest renders the report without inlined CSS otherwise |
|
|
|
|
See [`docs/20_docker_image.md`](../docs/20_docker_image.md) §8 for
|
|
the full table.
|