Docker Fundamentals
Volume 3 — Modern Development
Docker packages an application with everything it needs to run — runtime, libraries, config — into a single portable unit that behaves identically on your laptop, in CI, and in production. This chapter covers the mental model and the commands you'll actually use day to day.
1. Containers vs. Virtual Machines
A VM virtualizes hardware and runs a full guest operating system per instance — heavy, slow to start (minutes). A container shares the host machine's kernel and only packages the application and its libraries — light, starts in seconds. This is why you can run a dozen containers on a laptop that would struggle to run three or four full VMs.
2. Images vs. Containers
- Image — a read-only template: your application, its dependencies, and instructions for how to run it. Built once, stored, reused.
- Container — a running (or stopped) instance of an image, with its own writable layer on top.
The relationship is the same as a class and an object in code: one image, many containers can run from it simultaneously, each independent.
docker images # list images you have locally
docker ps # list running containers
docker ps -a # list all containers, including stopped ones
3. Writing a Dockerfile
A Dockerfile is the recipe for building an image. Here's a realistic multi-stage build for a .NET API — multi-stage means the final image only contains the compiled output, not the entire SDK used to build it:
# Stage 1: build
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY *.csproj .
RUN dotnet restore
COPY . .
RUN dotnet publish -c Release -o /app/publish
# Stage 2: runtime — much smaller, no SDK included
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
WORKDIR /app
COPY --from=build /app/publish .
EXPOSE 8080
ENTRYPOINT ["dotnet", "RoundTripAPI.dll"]
| Instruction | Purpose |
|---|---|
FROM | The base image to start from |
WORKDIR | Sets the working directory inside the image for subsequent instructions |
COPY | Copies files from your machine into the image |
RUN | Executes a command during the build (installing packages, compiling) |
EXPOSE | Documents which port the container listens on — informational, doesn't actually publish it |
ENTRYPOINT / CMD | What runs when a container starts from this image |
Copying *.csproj before the rest of the source (rather than one COPY . .) is deliberate: Docker caches each layer, and this ordering means dotnet restore only re-runs when dependencies actually change, not on every single code edit — a meaningfully faster rebuild loop.
4. Building and Running
docker build -t roundtrip-api:latest .
docker run -d -p 5000:8080 --name roundtrip-api roundtrip-api:latest
| Flag | Meaning |
|---|---|
-t | Tag the image with a name (and optional :version) |
-d | Run detached, in the background |
-p 5000:8080 | Map host port 5000 to the container's port 8080 (hostport:containerport) |
--name | Give the running container a friendly name instead of a random one |
5. Everyday Commands
docker logs roundtrip-api # view a container's stdout/stderr
docker logs -f roundtrip-api # follow logs live, same idea as tail -f
docker exec -it roundtrip-api bash # open an interactive shell inside a running container
docker stop roundtrip-api # gracefully stop a running container
docker rm roundtrip-api # remove a stopped container
docker rmi roundtrip-api:latest # remove an image
docker system prune # clean up unused containers, networks, and dangling images
docker exec -it is the single most useful debugging command — it drops you inside the running container exactly as it exists right now, letting you inspect files, check environment variables, or test connectivity from the container's own perspective rather than guessing from outside it.
6. Volumes and Bind Mounts
By default, anything written inside a container disappears when the container is removed — containers are meant to be disposable. Volumes and bind mounts persist data outside that lifecycle.
docker run -v roundtrip-data:/var/lib/data roundtrip-api # named volume, managed by Docker
docker run -v $(pwd)/local-folder:/app/data roundtrip-api # bind mount, maps directly to a folder on your machine
| Type | Use case |
|---|---|
| Named volume | Persistent data Docker manages for you — database data files, for example |
| Bind mount | Directly linking a folder on your machine into the container — the standard way to get live code reloading during local development |
7. Networking
docker network ls # list networks
docker network create traxs-local # create a custom network
docker run --network traxs-local ... # attach a container to it
Containers on the same custom network can reach each other by container name as a hostname — this is the mechanism that makes docker-compose.yml service names resolve to each other (covered in the next chapter), rather than each container needing to know the others' IP addresses.
8. OrbStack for Local Traxs Development
OrbStack is the Docker runtime used for local development across Traxs projects (macOS). It's a drop-in replacement for Docker Desktop — the same docker CLI commands, images, and Dockerfiles work identically; OrbStack just runs the underlying virtualization more efficiently on macOS.
# All standard docker commands work exactly the same under OrbStack
docker ps
docker compose up -d
If a container behaves unexpectedly only under OrbStack and not under a teammate's Docker Desktop (or vice versa), that's worth flagging specifically — the two are meant to be compatible, but resource limits (memory/CPU allocated to the VM) differ by default and are worth checking first.
9. Troubleshooting Playbook
| Symptom | Likely cause | What to check |
|---|---|---|
docker: command not found | OrbStack/Docker not running, or CLI not on PATH | Confirm OrbStack is running; which docker |
Container exits immediately after docker run | The main process finished or crashed immediately | docker logs <name> right after — the exit reason is almost always in there |
Can't reach the app at localhost:5000 | Port mapping missing or wrong, or the app isn't listening on the port you expect inside the container | Confirm -p hostport:containerport matches what the app actually binds to inside the container (check EXPOSE and app startup config) |
| Changes to code don't show up in the running container | Rebuilt without a bind mount, or the image was cached | For live development, use a bind mount rather than rebuilding on every change; otherwise docker build again |
| "port is already allocated" | Another container or local process already using that host port | docker ps to check running containers; lsof -i :5000 to check local processes (see the Bash Reference chapter) |
| Container can't reach another container by name | Both aren't on the same custom Docker network | docker network ls and docker inspect <container> to confirm network membership |
| Disk filling up over time | Old stopped containers, dangling images, and unused volumes accumulating | docker system prune (add --volumes to also remove unused volumes, with care) |
permission denied inside the container writing to a bind-mounted folder | UID/GID mismatch between the container's user and the host folder's ownership | Adjust the Dockerfile's user, or the host folder's permissions, to align |
10. Quick Reference
| Category | Command | Purpose |
|---|---|---|
| Build | docker build -t name:tag . | Build an image from a Dockerfile |
| Run | docker run -d -p host:container name | Run a container, detached, with port mapping |
| Inspect | docker ps -a | List all containers, running or stopped |
| Inspect | docker logs -f name | Follow a container's logs live |
| Debug | docker exec -it name bash | Open a shell inside a running container |
| Lifecycle | docker stop name / docker rm name | Stop / remove a container |
| Images | docker images / docker rmi name | List / remove images |
| Cleanup | docker system prune | Remove unused containers, networks, dangling images |
| Storage | -v volume:/path | Named volume, Docker-managed |
| Storage | -v $(pwd)/dir:/path | Bind mount, maps a local folder directly |
| Network | docker network create name | Create a custom network for containers to reach each other by name |
Part of the Traxs Engineering Handbook — Volume 3: Modern Development. Companion chapters in this volume: Docker Compose, REST APIs, Authentication & Authorization, JSON & YAML, Postman Guide.