Canonical infra facts (projects, buckets, VM inventory, regions, the compute budget) live in infrastructure.md. This doc is the Docker build/run how-to.
Docker packages the entire training environment (PyTorch, CUDA, GDAL, Python libraries) into a single image. This means:
- Reproducibility: The exact same environment runs on the L4 dev VM, the A100 production VM, and any future machine
- No "works on my machine" bugs: If it runs in the container, it runs everywhere
- Clean VMs: No need to install dependencies on each VM manually
1. Develop and test code directly on L4 VM (no Docker, fast iteration)
2. When code is ready → build Docker image via Cloud Build
3. Pull image on production VM → run training
You do not develop inside Docker. You do not build Docker locally on Windows. Docker is only for packaging tested code into a reproducible environment for production runs.
This is where you spend most of your time. See vm_instruction.md for full setup.
Connect via VSCode Remote-SSH, edit code, and run directly:
source ~/ml-env/bin/activate
cd ~/RTSmappingDL
python scripts/check_data.py --config configs/baseline.yaml
python scripts/train.py --config configs/baseline.yaml # short test runKey requirement: Keep ~/ml-env aligned with requirements.txt so that code that works on the VM will also work in the Docker container. Install new packages in both places — pip install <package> on the VM, and add it to requirements.txt for Docker.
- Dockerfile: computing/Dockerfile.train — base image
nvcr.io/nvidia/pytorch:24.05-py3(Python 3.10), plus geospatial system deps and gcsfuse (modern keyring — see the file). - Python deps: requirements.txt (human-edited spec with
version ranges; used in the Docker build to stay compatible with Python 3.10).
requirements_frozen.txtis generated on the L4 VM (Python 3.12) and is NOT used in Docker since the Python versions differ; each training run logs an in-container freeze as an MLflow artifact viamlflow_utils.log_requirements_frozen(). - Build context exclusions: .dockerignore.
You build with Cloud Build (Google's remote build service), not on your VM. This avoids consuming VM disk and compute.
gcloud config set project pdg-project-406720
gcloud auth configure-docker us-west1-docker.pkg.devFrom the repo root on the L4 VM:
gcloud builds submit \
--tag us-west1-docker.pkg.dev/pdg-project-406720/pdg-artifact-registry/rts-train:v2 \
--dockerfile computing/Dockerfile.train \
. --timeout=1800Takes ~10–15 minutes. The --timeout=1800 gives 30 minutes for the build.
gcloud artifacts docker images list \
us-west1-docker.pkg.dev/pdg-project-406720/pdg-artifact-registrySSH into the production VM (see vm_instruction.md Part 7), then:
docker pull us-west1-docker.pkg.dev/pdg-project-406720/pdg-artifact-registry/rts-train:v2docker run --rm --gpus all \
us-west1-docker.pkg.dev/pdg-project-406720/pdg-artifact-registry/rts-train:v2 \
python -c "import torch; print(f'CUDA: {torch.cuda.is_available()}, GPUs: {torch.cuda.device_count()}')"MLflow tracking URI lives in configs/baseline.yaml:mlflow.tracking_uri
(read by training/mlflow_utils.py). Don't pass it as an env var — that's
ignored by our code and creates a second source of truth.
Standard production run (config fully controls MLflow URI, output dir, and GCS data path):
IMAGE=us-west1-docker.pkg.dev/pdg-project-406720/pdg-artifact-registry/rts-train:v2
sudo docker run -d --gpus '"device=0"' --privileged \
--shm-size=16g \
--name <run_name> \
-v ~/RTSmappingDL:/app \
-v /mnt/outputs:/outputs \
-v ~/.config/gcloud/application_default_credentials.json:/gcp_adc.json:ro \
-e GOOGLE_APPLICATION_CREDENTIALS=/gcp_adc.json \
--entrypoint bash \
$IMAGE \
-c "sed -i 's/LayerId = cv2.dnn.DictValue/LayerId = object/' \
/usr/local/lib/python3.10/dist-packages/cv2/typing/__init__.py 2>/dev/null || true && \
python scripts/train.py \
--config configs/<config>.yaml \
--out-dir /outputs/<run_name> 2>&1 | tee /outputs/<run_name>.log"
# Monitor
sudo docker logs -f <run_name>Notes:
--shm-size=16grequired — default 64MB causes bus errors with 8 DataLoader workers.-v ~/RTSmappingDL:/appmounts the repo so the latest code/configs are used without rebuilding.- cv2 patch (
sed -i ...) is needed forrts-train:v2(predates the baked fix). The inference imagerts-infer:v1(built 2026-06-26 fromDockerfile.train) is self-contained — cv2 patch baked,inference/included, MLflow 2.22.5 — so the fleet workers need no runtime sed/mount/pip. - GCS ADC credentials: run
gcloud auth application-default login --no-launch-browseronce on the VM, then mount~/.config/gcloud/application_default_credentials.json. - MLflow tracking URI is
file:///outputs/mlflow(in configs); metrics are persisted to/mnt/outputs/mlflowon the host.
docker run --rm --gpus all \
--shm-size=32g \
--privileged \
-v /mnt/outputs:/outputs \
us-west1-docker.pkg.dev/pdg-project-406720/pdg-artifact-registry/rts-train:v2 \
-m torch.distributed.run \
--nproc_per_node=8 \
scripts/train.py --config configs/baseline.yaml--shm-size=32g is required for DataLoader workers with multi-GPU.
docker run -d --gpus all \
--privileged \
--shm-size=32g \
--name rts-training \
-v /mnt/outputs:/outputs \
us-west1-docker.pkg.dev/pdg-project-406720/pdg-artifact-registry/rts-train:v2 \
scripts/train.py --config configs/baseline.yaml
# Monitor
docker logs -f rts-training
# Stop
docker stop rts-training| Container Path | Source | Mode | Purpose |
|---|---|---|---|
/data |
GCS via gcsfuse (mounted inside container) | read | Training data |
/outputs |
/mnt/outputs on host VM |
read/write | Checkpoints, logs |
| Variable | Value | Purpose |
|---|---|---|
GOOGLE_APPLICATION_CREDENTIALS |
Path to service account JSON | GCS authentication |
(MLflow tracking URI lives in configs/baseline.yaml:mlflow.tracking_uri,
not in env vars.)
- Create a service account with Storage Object Viewer + Storage Object Creator roles
- Download the JSON key file
- Mount the key file into the container and set the environment variable
1. Edit code on L4 VM via VSCode Remote-SSH
2. Test directly on L4 (no Docker rebuild needed)
3. When ready for production (see Part 3):
IMAGE=us-west1-docker.pkg.dev/pdg-project-406720/pdg-artifact-registry/rts-train:v2
4. On production VM:
docker pull us-west1-docker.pkg.dev/pdg-project-406720/pdg-artifact-registry/rts-train:v2
5. Run training
The key insight: You only rebuild the Docker image when moving from development to production. During development, Docker is not in the loop.
| Problem | Cause | Fix |
|---|---|---|
| Cloud Build timeout | Large image | Add --timeout=1800 |
CUDA out of memory |
Batch too large | Reduce batch_size in config |
NCCL timeout |
Multi-GPU communication failure | Set NCCL_DEBUG=INFO to diagnose |
Permission denied on /outputs |
Volume ownership mismatch | Add --user $(id -u):$(id -g) |
rasterio import error |
GDAL missing | Verify Dockerfile has libgdal-dev |
| Image not found on VM | Auth issue | Run gcloud auth configure-docker |
| gcsfuse fails in container | Missing flag | Add --privileged to docker run |
- Code runs on L4 VM without errors
- All imports work
-
scripts/check_data.pypasses - Training loop runs for a few steps
-
requirements.txtis complete
- Image appears in GCR (
gcloud container images list-tags) - GPU accessible in container
- Training runs and saves checkpoints
- MLflow logs appear at
configs/baseline.yaml:mlflow.tracking_uri