The Kubernetes Fundamentals domain in my KCNA study notes assumes container runtime knowledge going in - Docker, in practice, since it’s still the most common way people get there. The Kubernetes Fundamentals series is the companion to this post, one level up the stack.
Prerequisites
- WSL2 with Ubuntu on Windows, or native Linux/macOS
sudoaccess on the machine you’re installing on- A Docker Hub account if you want to push images
Installing Docker
On WSL2/Ubuntu, install Docker Engine directly rather than Docker Desktop - run this from your home directory in the WSL2 shell:
sudo apt update
sudo apt install -y ca-certificates curl gnupg
# Add Docker's GPG key to a keyring
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
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# Add the repo - $(. /etc/os-release && echo $VERSION_CODENAME) resolves to your release codename
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
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Without this next step, every docker command needs sudo. Add your user to the docker group, then start a new shell session for it to take effect:
sudo usermod -aG docker $USER
On native macOS or Windows without WSL2, Docker Desktop is the simpler path - Mac install or Windows install.
Verify the install:
docker version
docker run hello-world
Core commands
Images
docker pull nginx:latest # pull only - doesn't start a container
docker images # list local images
docker image history nginx # inspect the layers that make up an image
docker rmi nginx # remove an image
Containers
docker run nginx # attached - blocks this terminal
docker run -d nginx # detached - returns immediately
docker run -d --rm nginx # detached, auto-removed when it exits
docker run -it ubuntu bash # interactive shell inside a new container
docker exec -it <container> bash # shell into an already-running container
docker ps # running containers
docker ps -a # all containers, including stopped ones
docker logs <container> # a container's stdout/stderr
docker stop <container> # stop a running container
docker rm <container> # remove a stopped container
Ports, volumes, and environment variables
docker run -d -p 8080:80 nginx # map host port 8080 to container port 80
docker run -d -p 8080:80 -v $(pwd)/my-site:/usr/share/nginx/html nginx # bind-mount a local folder into the container
docker run -e MYSQL_ROOT_PASSWORD=changeme mysql # set an environment variable
Try the volume mount end to end:
mkdir my-site && echo "hello from my site" > my-site/index.html
docker run -d --rm -p 8080:80 -v $(pwd)/my-site:/usr/share/nginx/html nginx
curl http://localhost:8080
Building images
CMD vs ENTRYPOINT
CMD sets a default command that’s replaced entirely if you pass a command at docker run:
FROM alpine
CMD ["sleep", "5"]
docker run myimage sleeps for 5 seconds. docker run myimage echo hi runs echo hi instead - CMD is ignored completely, not appended to.
ENTRYPOINT is fixed - anything passed at docker run is appended to it as an argument, rather than replacing it:
FROM alpine
ENTRYPOINT ["sleep"]
CMD ["5"]
docker run myimage runs sleep 5. docker run myimage 10 runs sleep 10 - CMD here is just ENTRYPOINT’s default argument, overridable without touching the fixed command itself.
Multi-stage builds
Building cmatrix (a terminal screensaver - harmless example, real teaching point, adapted from James Spurin’s Dive Into… courses) from source needs a full compiler toolchain: alpine-sdk, autoconf, ncurses-dev. None of that needs to ship in the image that actually runs the binary. A multi-stage build keeps the build toolchain in one stage and copies out only the compiled result:
# Stage 1 - build
FROM alpine AS builder
WORKDIR /cmatrix
RUN apk add --no-cache git autoconf automake alpine-sdk ncurses-dev ncurses-static && \
git clone https://github.com/spurin/cmatrix.git . && \
autoreconf -i && \
mkdir -p /usr/lib/kbd/consolefonts /usr/share/consolefonts && \
./configure LDFLAGS="-static" && \
make
# Stage 2 - runtime
FROM alpine
LABEL org.opencontainers.image.description="Container image for cmatrix"
RUN apk add --no-cache ncurses-terminfo-base && \
adduser -D -H -s /usr/sbin/nologin appuser
COPY --from=builder /cmatrix/cmatrix /usr/local/bin/cmatrix
USER appuser
ENTRYPOINT ["cmatrix"]
CMD ["-b"]
From the same directory as the Dockerfile above:
docker build -t your-dockerhub-username/cmatrix .
docker run --rm -it your-dockerhub-username/cmatrix
The final image only carries alpine plus the compiled binary - none of the build-time packages from stage 1 make it into the image that ships.
Multi-architecture builds
docker build produces an image for your local architecture only. buildx builds and pushes more than one architecture from the same Dockerfile in one command. From the same directory as the Dockerfile above:
docker login
docker buildx create --name multiarch-builder --use
docker buildx build --platform linux/amd64,linux/arm64 -t your-dockerhub-username/cmatrix:latest --push .
Docker Compose
Compose runs more than one container from a single file. Current Compose doesn’t need a version: key, and containers on the same Compose file share a default network automatically - they reach each other by service name, with no manual links: configuration:
services:
web:
image: nginx
ports:
- "8080:80"
depends_on:
- cache
cache:
image: redis:alpine
docker compose up -d
docker compose down
web can reach cache at the hostname cache - Compose’s built-in DNS resolves service names within the file automatically.
Registries
| Registry | Notes |
|---|---|
| Docker Hub | The default public registry - docker pull/docker push use it unless you specify otherwise |
| Private registries | Amazon ECR, Azure Container Registry, Google Artifact Registry are the managed options on each major cloud |
| Self-hosted | Run your own with docker run -d -p 5000:5000 --name registry registry:2 |
docker login <registry-url>
docker tag myimage:latest <registry-url>/myimage:latest
docker push <registry-url>/myimage:latest
Cleanup
docker system prune # remove stopped containers, unused networks, dangling images, build cache
docker system prune -a # also remove unused images, not just dangling ones
Resources
- Docker official docs - the canonical reference for everything covered here
- Compose file reference - the full Compose Spec
- Multi-platform builds with buildx
- James Spurin’s Dive Into… courses - hands-on Docker and Kubernetes training
- KodeKloud Docker course - hands-on labs and practice exercises
Notes
- This covers Docker specifically, not Podman or containerd directly - KCNA’s Container Orchestration domain covers the CRI abstraction that lets Kubernetes work with any of them interchangeably.