K8s Learning: Step 05 — Build and Push Docker Image
05 — Build and Push Docker Image
Builds your Docker image locally, transfers it to the VM, and loads it into k3s's container runtime — all without a container registry.
Required environment variables:
K3S_SSH_USER,K3S_SSH_HOSTNAME— SSH access to the VMK3S_PROJECT_BASE_DIRECTORY— path to the DockerfileK3S_PROJECT_DOMAIN— your app's domain nameK3S_UNIQUE_DOCKER_IMAGE_NAME— image name (e.g.,blog-farewell-cloud)K3S_TEMP_BUILD_DIRECTORY— temp dir for the tar.gz
The Approach: No Registry Needed
Avoids registry accounts, private registry costs, and public image exposure. Perfect for single-server setups.
1. Build the Docker image
IMAGE_NAME="$K3S_UNIQUE_DOCKER_IMAGE_NAME:latest"
docker build --platform linux/amd64 -t $IMAGE_NAME $K3S_PROJECT_BASE_DIRECTORY
--platform linux/amd64 is important on Apple Silicon Macs (ARM). Your VM runs x86_64 — without this flag, the image would be ARM and wouldn't run on the VM.
2. Export and compress
docker save $IMAGE_NAME | gzip > $IMAGE_TAR_GZ
docker save exports all layers and metadata as a tar archive. Piping through gzip typically compresses a 500 MB image to ~150 MB.
3. Copy to the VM
scp $IMAGE_TAR_GZ $K3S_SSH_USER@$K3S_SSH_HOSTNAME:~
4. Load into containerd
ssh ... "gzip -d < ~/$K3S_UNIQUE_DOCKER_IMAGE_NAME.tar.gz | \
sudo k3s ctr -n k8s.io images import -"
k3s uses containerd, not Docker. k3s ctr is the containerd CLI:
gzip -d— decompressk3s ctr -n k8s.io images import -— import into containerd's image store-n k8s.io— the containerd namespace where k3s looks for images
5. Verify
ssh ... "sudo k3s ctr -n k8s.io images ls | grep $K3S_UNIQUE_DOCKER_IMAGE_NAME"
⚠️ Why imagePullPolicy: Never? The Deployment in step 06 must set this to prevent Kubernetes from trying to pull from Docker Hub. The image exists only locally on the VM.