View on GitHub

Containers & Kubernetes Tutorial

Lab 00 – Course setup (before Session 01)

Table of Contents

Goals

Pre-requisites

Continuity. This is where the Pinboard project starts. Nothing is built yet: this lab only prepares the machine and puts the course sources on disk at ~/docker-kubernetes-training. From Lab 01 onwards you build the Pinboard application — database, API and web — one layer at a time, and every later lab assumes the tools installed here.

Guide

Step 01: Check the lab machine

Did you receive an Azure lab VM from the instructor? Then everything in this lab is already installed. Connect with the SSH line you were sent (ssh student@<prefix>-<you>.westeurope.cloudapp.azure.com + password), read the login banner — it must say status : READY — and jump straight to Step 02 to confirm the repo is there, then Step 10. Two things differ on a remote VM:

  • Wherever a lab says “open http://localhost:8080”, either forward the port when you connect — ssh -L 8080:localhost:8080 -L 80:localhost:80 student@<host> — and open it on your laptop, or use the VM’s public name directly: http://<prefix>-<you>.westeurope.cloudapp.azure.com:8080 (port 80 works from Lab 07 on).
  • The VM shuts down every evening at 21:00 (Lisbon). Ask the instructor to start it if you want to work late.

If the banner says RUNNING: …, wait a few minutes; if it says FAILED: …, tell the instructor (they can re-run sudo /usr/local/sbin/lab-setup.sh).

Confirm the distribution and the architecture. Every command in this course is Linux/bash; the architecture matters because kind and k9s are downloaded as release binaries.

cat /etc/os-release | head -3
dpkg --print-architecture
free -h | head -2
df -h /var/lib | tail -1
PRETTY_NAME="Ubuntu 24.04.3 LTS"
NAME="Ubuntu"
VERSION_ID="24.04"
amd64
               total        used        free      shared  buff/cache   available
Mem:            15Gi       1.2Gi        12Gi        12Mi       2.1Gi        14Gi
/dev/sda1        79G        12G        64G  16% /

Install the handful of base packages the rest of this lab needs:

sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg git jq

Note. docker also exists in Ubuntu’s own repositories as docker.io. Do not use it: it lags several minor versions behind, and this course needs Docker Engine 28+ with the Compose v2 and Buildx plugins. Steps 03/04 install the packages from Docker’s official repository instead.

Step 02: Clone the course repository

Every lab refers to files under ~/docker-kubernetes-training/src/..., so clone it into your home directory with exactly that name.

git clone https://github.com/zonoth/docker-kubernetes-training.git ~/docker-kubernetes-training
Cloning into '/home/student/docker-kubernetes-training'...
remote: Enumerating objects: 412, done.
remote: Counting objects: 100% (412/412), done.
remote: Compressing objects: 100% (238/238), done.
Receiving objects: 100% (412/412), 1.84 MiB | 6.10 MiB/s, done.
Resolving deltas: 100% (121/121), done.

Note. The URL above is a placeholder. The instructor gives you the real clone URL (it may be a different host, or an HTTPS URL with a token) at the start of Session 01 — use that one and keep the target folder name ~/docker-kubernetes-training.

Have a quick look at what you just cloned:

ls ~/docker-kubernetes-training
README.md  decks  demos  docs  labs  landing  slides  src

Step 03: The fast path — labs/setup.sh

The repository ships a script that performs everything in Steps 04 to 09 on Debian/Ubuntu. It is idempotent — safe to run again after a failure, after a partial install, or on a rebuilt machine.

bash ~/docker-kubernetes-training/labs/setup.sh
==> Lab machine setup
    distribution : Ubuntu 24.04.3 LTS  (ubuntu/noble)
    architecture : amd64
    user         : student
    options      : k9s=yes vscode=no kubectl=v1.33

==> 1/7 Base packages (git, curl, jq, …)
==> 2/7 Docker Engine
    adding Docker's GPG key
    writing /etc/apt/sources.list.d/docker.list
    adding student to the 'docker' group
==> 3/7 kubectl (v1.33 stable channel)
==> 4/7 kind
    installing kind v0.29.0 (linux/amd64)
==> 5/7 helm
    running the official get-helm-3 script
==> 6/7 k9s (optional)
    installing k9s v0.50.9 (Linux_x86_64)
==> 7/7 Visual Studio Code (optional)
    skipped (set INSTALL_VSCODE=yes to install it)

==> Installed versions
Docker version 28.3.2, build 578ccf6
Docker Compose version v2.39.1
github.com/docker/buildx v0.26.1
  gitVersion: v1.33.3
  kustomizeVersion: v5.6.0
kind v0.29.0 go1.24.5 linux/amd64
v3.18.4+g8d3d1cb
git version 2.43.0

==> Done
    student was added to the 'docker' group. Group membership is only
    picked up by NEW logins, so do one of these before running docker commands:

        newgrp docker      # applies to the current shell only
        # or log out and back in (best), or reboot the VM

    Next: verify with 'docker run --rm hello-world', then open labs/lab01.md

Two switches are available if you want them:

INSTALL_VSCODE=yes bash ~/docker-kubernetes-training/labs/setup.sh   # also install VS Code + extensions
INSTALL_K9S=no     bash ~/docker-kubernetes-training/labs/setup.sh   # skip k9s

Warning. Run the script as your own user, not with sudo bash setup.sh. It calls sudo itself where it needs root, and it must know your real user name to add it to the docker group.

If the script finished without errors, jump to Step 10 (after newgrp docker or a re-login). Steps 04–09 below are the same install done by hand — read them anyway if you like to know what a script is doing to your machine, and use them if the script fails on your distribution.

Step 04: Install Docker Engine 28+

Add Docker’s official apt repository, then install the engine plus the CLI plugins. docker-buildx-plugin gives you BuildKit features used in Session 02; docker-compose-plugin gives you docker compose (v2, no hyphen) used in Session 04.

sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" \
  | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Setting up containerd.io (1.7.28-1) ...
Setting up docker-ce-cli (5:28.3.2-1~ubuntu.24.04~noble) ...
Setting up docker-buildx-plugin (0.26.1-1~ubuntu.24.04~noble) ...
Setting up docker-compose-plugin (2.39.1-1~ubuntu.24.04~noble) ...
Setting up docker-ce (5:28.3.2-1~ubuntu.24.04~noble) ...
Created symlink /etc/systemd/system/multi-user.target.wants/docker.service → /usr/lib/systemd/system/docker.service.

Note. On Debian replace both occurrences of ubuntu in the URLs with debian. The $(. /etc/os-release && …) part resolves your codename (noble, jammy, bookworm, …) automatically.

The engine should already be running under systemd:

sudo systemctl enable --now docker
systemctl is-active docker
active

Step 05: Run Docker without sudo

The Docker CLI talks to dockerd over the socket /var/run/docker.sock, which is owned by root:docker. Adding yourself to the docker group means you never type sudo docker in this course.

sudo usermod -aG docker $USER
newgrp docker           # apply the new group to THIS shell (or log out and back in)

Warning. Membership of the docker group is equivalent to root on the host — anyone in it can start a privileged container that mounts /. That is fine on a disposable lab VM; on a shared or production machine, prefer rootless Docker or sudo. We call this out again in Session 09 (security basics).

Check it works and that you get the same answer as root:

docker run --rm hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
17eec7bbc9d7: Pull complete
Digest: sha256:940c619fbd418f9b2b1b63e25d8861f9cc1b46e3fc8b018ccfe8b78f19b8cc4f
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.
...

If you get permission denied while trying to connect to the Docker daemon socket, your shell has not picked up the new group: run newgrp docker, or log out and back in.

Step 06: Install kubectl

kubectl comes from the community package host pkgs.k8s.io, which is versioned per minor release — you subscribe to a channel, here v1.33 (the version our kind cluster runs from Session 05 on). Keep the client within one minor version of the cluster.

curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.33/deb/Release.key \
  | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
sudo chmod a+r /etc/apt/keyrings/kubernetes-apt-keyring.gpg

echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.33/deb/ /' \
  | sudo tee /etc/apt/sources.list.d/kubernetes.list > /dev/null

sudo apt-get update
sudo apt-get install -y kubectl
kubectl version --client
Client Version: v1.33.3
Kustomize Version: v5.6.0

Note. There is no cluster yet, so plain kubectl version will end with connection refused — that is expected until Lab 05 creates the kind cluster.

Step 07: Install kind

kind (Kubernetes in Docker) runs a real Kubernetes cluster inside Docker containers — one container per node. It is distributed as a single static binary from GitHub Releases, so we fetch the latest release for your architecture.

KIND_VERSION=$(curl -fsSL https://api.github.com/repos/kubernetes-sigs/kind/releases/latest | jq -r .tag_name)
echo "installing kind ${KIND_VERSION} for linux/$(dpkg --print-architecture)"
curl -fsSLo /tmp/kind "https://kind.sigs.k8s.io/dl/${KIND_VERSION}/kind-linux-$(dpkg --print-architecture)"
sudo install -m 0755 /tmp/kind /usr/local/bin/kind && rm /tmp/kind
kind version
installing kind v0.29.0 for linux/amd64
kind v0.29.0 go1.24.5 linux/amd64

Do not create a cluster yet — Lab 05 does that with labs/kind-config.yaml, which adds the port mappings the Ingress lab needs.

Step 08: Install helm (and k9s)

Helm is used in Session 07 (installing ingress-nginx) and Session 10 (packaging). Its official installer script picks the right binary for your architecture.

curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | sudo bash
helm version --short
Downloading https://get.helm.sh/helm-v3.18.4-linux-amd64.tar.gz
Verifying checksum... Done.
Preparing to install helm into /usr/local/bin
helm installed into /usr/local/bin/helm
v3.18.4+g8d3d1cb

k9s (optional but recommended) — a terminal UI for Kubernetes. We use it in Session 09 instead of the retired Kubernetes Dashboard.

K9S_VERSION=$(curl -fsSL https://api.github.com/repos/derailed/k9s/releases/latest | jq -r .tag_name)
ARCH=$([ "$(dpkg --print-architecture)" = "amd64" ] && echo x86_64 || echo arm64)
curl -fsSL "https://github.com/derailed/k9s/releases/download/${K9S_VERSION}/k9s_Linux_${ARCH}.tar.gz" \
  | tar -xz -C /tmp k9s
sudo install -m 0755 /tmp/k9s /usr/local/bin/k9s && rm /tmp/k9s
k9s version -s
Version:    v0.50.9
Commit:     2a5c2a8f0d6d6bd8c1b0e3b6d3d24f9b9a3a3f21
Date:       2026-05-14T09:12:44Z

Step 09: Install VS Code and the extensions (optional)

Any editor works — if you are happy in vim or nano, skip this step. VS Code is what the instructor demos with, and the Kubernetes extension makes YAML far less painful.

curl -fsSL https://packages.microsoft.com/keys/microsoft.asc \
  | sudo gpg --dearmor -o /etc/apt/keyrings/packages.microsoft.gpg
sudo chmod a+r /etc/apt/keyrings/packages.microsoft.gpg

echo "deb [arch=amd64,arm64 signed-by=/etc/apt/keyrings/packages.microsoft.gpg] https://packages.microsoft.com/repos/code stable main" \
  | sudo tee /etc/apt/sources.list.d/vscode.list > /dev/null

sudo apt-get update
sudo apt-get install -y code

Then add the three extensions used through the course:

code --install-extension ms-azuretools.vscode-docker
code --install-extension ms-kubernetes-tools.vscode-kubernetes-tools
code --install-extension redhat.vscode-yaml
Installing extensions...
Extension 'ms-azuretools.vscode-docker' v2.0.0 was successfully installed.
Extension 'ms-kubernetes-tools.vscode-kubernetes-tools' v1.3.28 was successfully installed.
Extension 'redhat.vscode-yaml' v1.18.0 was successfully installed.

Open the course repo with code ~/docker-kubernetes-training.

Step 10: Verify everything

Run this block as-is. Every line must print a version; nothing should say command not found or permission denied.

docker --version
docker compose version
docker buildx version
kubectl version --client
kind version
helm version --short
git --version
docker run --rm hello-world | head -2
docker info --format 'Server  · runtime  · storage  · cgroups v'
Docker version 28.3.2, build 578ccf6
Docker Compose version v2.39.1
github.com/docker/buildx v0.26.1 fa0e0e5b1d2b3c9e0f2a1e0b0f9a2f36f5f0b1a2
Client Version: v1.33.3
Kustomize Version: v5.6.0
kind v0.29.0 go1.24.5 linux/amd64
v3.18.4+g8d3d1cb
git version 2.43.0

Hello from Docker!
Server 28.3.2 · runtime runc · storage overlay2 · cgroups v2

The last line is worth a look: the CLI you type talks to dockerd 28.3.2, which delegates to containerd and finally runc, on an overlay2 union filesystem with cgroup v2 limits. Session 01 explains every word of that sentence — you now have the machine that proves it.

Note (Docker Hub account — optional). Everything in this course builds locally and, from Session 05, is loaded into kind with kind load docker-image; no registry is required. Only the last, optional step of Lab 02 pushes an image to Docker Hub. If you want to do it, create a free account at https://hub.docker.com now and remember your Docker ID — you will run docker login in that step.

Check yourself: you ran sudo usermod -aG docker $USER but docker ps still says "permission denied". Why? Group membership is evaluated when a **login session** is created, not when the `/etc/group` file changes. Your current shell still carries the old group set (check with `id -nG`). Fix it with `newgrp docker` for this shell, or log out and back in (or reboot the VM) for every shell. Using `sudo docker ps` would also work, but then the images you build as root are invisible to your user later — don't mix the two.

Stretch goal

Conclusion

What you have now

Nothing to clean up — no containers were left running (hello-world used --rm). Keep the images you pulled; the next labs reuse them.

If something here failed, fix it before Session 01 rather than during it: the labs build on each other and there is no time budget for installs.

Next: Lab 01 – Run the Pinboard database tier.