Skill Path (EasyMedium)

Docker 101: Build Container Images

Introduction

Knowing how to run containers is only half the story - sooner or later, you'll need to package your own applications into container images. This skill path teaches you Dockerfile authoring from the ground up, starting with the simplest possible image and building up to production-grade multi-stage builds. You'll learn how to:

  • Build and publish a container image to a registry
  • Write Dockerfiles using core instructions: FROM, COPY, RUN, and CMD
  • Handle application and system-level dependencies in a Dockerfile
  • Compile and build applications inside a Dockerfile
  • Inspect container image internals (layers, sizes, digests)
  • Optimize images with multi-stage builds to produce smaller, cleaner production artifacts

By the end of this skill path, you'll be comfortable writing Dockerfiles for real-world applications and understand how to keep your images lean and efficient.

Container image composition - base image layers, dependency layers, and application layers.

Prerequisites

Build and Publish a Container Image

Before diving into Dockerfile authoring, let's get familiar with the core workflow: building an image from a Dockerfile and pushing it to a container registry. This challenge walks you through the entire process using a pre-written Dockerfile - so you can focus on the docker build and docker push commands and the image naming convention.

Container image name format: registry/repository:tag

Write Your First Dockerfile

Now it's time to write your own Dockerfile from scratch. Every container image starts with a base image (FROM), some files to copy in (COPY), and a default command to run (CMD) - that's really all you need to containerize a simple application.

Container image composition: base image layer, dependency layers, and application layer.

In this challenge, you'll containerize a minimal Node.js server using just three Dockerfile instructions:

Write a Dockerfile for a Web Server Application

Most real-world applications aren't single-file scripts - they come with dependencies that need to be installed during the image build. This is where the RUN instruction comes in, allowing you to execute arbitrary commands (like npm install) as part of the build process.

In this challenge, you'll write a Dockerfile for an Express.js web server that has external npm dependencies:

Exclude Dev Dependencies from the Production Image

Development tools like linters, test frameworks, and hot-reload utilities have no place in a production container image - they bloat the image size and expand the attack surface. Most package managers provide a way to install only production dependencies, and knowing how to use it in a Dockerfile is a practical skill worth acquiring early.

In this challenge, you'll write a Dockerfile that keeps the production image lean by excluding all development dependencies:

Handle System-Level Dependencies in a Dockerfile

Some application libraries depend on system-level packages (C libraries, shared objects, etc.) that aren't included in the base image. When this happens, you need to install OS-level packages with apt-get or similar tools before installing the application's own dependencies.

In this challenge, you'll write a Dockerfile for a Python application that requires both system-level and Python-level packages:

Build and Compile Applications in Dockerfiles

So far, the applications you've containerized were interpreted (Node.js, Python) and didn't require a compilation step. But many production applications are written in compiled languages like Go, Rust, Java, or TypeScript, and their source code must be built into a binary or a bundle before it can run.

Building a Go application inside a Dockerfile: the golang base image includes the compiler, and the resulting binary is baked into the same image.

In this challenge, you'll compile both a Go backend and a TypeScript frontend inside their Dockerfiles:

Build and Inspect a Container Image

Understanding what's inside a container image - its layers, sizes, and contents - is essential for troubleshooting build issues and optimizing image size. Tools like docker image inspect and dive let you peek under the hood and see exactly how your Dockerfile instructions translate into image layers.

In this challenge, you'll build an image, then use inspection tools to answer questions about its internal structure:

How to Build Smaller Container Images

Loading tutorial...

If you completed the previous challenge, you may have noticed that single-stage Dockerfiles for compiled applications produce unnecessarily large images - the final image includes the compiler, build tools, and source code that are no longer needed at runtime.

Docker's multi-stage builds solve this problem by allowing you to use multiple FROM instructions in a single Dockerfile. This tutorial explains the technique in depth, with practical examples for Node.js, Go, Python, Rust, and other stacks.

Optimize Container Images with Multi-Stage Builds

Time to put the theory into practice! Remember the Go backend and TypeScript frontend you containerized earlier using single-stage Dockerfiles? Those images included compilers, build tools, and dev dependencies that bloat the final artifact.

Multi-stage build: a build stage with the compiler and source code, and a runtime stage with only the compiled binary.

In the capstone challenge of this skill path, you'll rewrite both Dockerfiles using multi-stage builds to produce smaller, production-ready images: