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 helps you learn Dockerfile authoring from the ground up, starting with the simplest possible image and building up to production-grade multi-stage builds and multi-platform support. You'll learn how to:
- Build and publish a container image to a registry
- Write Dockerfiles using core instructions:
FROM,COPY,RUN, andCMD - 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
- Use
ENTRYPOINT,ENV,USER,STOPSIGNAL, andARGfor production-ready Dockerfiles - Understand container image internals: layers, configs, manifests, and image indexes
- Build images for other platforms using QEMU emulation, cross-compilation, and remote builders
- Build and push multi-platform container images
By the end of this skill path, you'll be comfortable writing Dockerfiles for real-world applications, understand image internals, and know how to build for any target platform.

Prerequisites
- Linux command-line knowledge
- Familiarity with installing OS packages and tar archiving/unarchiving
- Basic container running skills (see Docker 101: Run and Manage Containers)
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.

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.

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.

In this challenge, you'll compile both a Go backend and a TypeScript frontend inside their Dockerfiles:
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.

In this midpoint capstone challenge of the skill path, you'll rewrite both Dockerfiles using multi-stage builds to produce smaller, production-ready images:
Write a Dockerfile for a CLI Tool
Not every container runs a long-lived server - many are used to package and distribute command-line tools.
In this challenge, you'll write a Dockerfile for a Go CLI tool
and learn how ENTRYPOINT improves the UX of containerized command-line applications:
Write a Dockerfile with Sensible Runtime Defaults
A well-crafted container image doesn't just package the application code -
it also sets sensible runtime defaults.
Instructions like ENV, USER, and STOPSIGNAL let you bake configuration,
security settings, and graceful shutdown behavior directly into the image,
so the container works correctly out of the box without extra flags at docker run time.
In this challenge, you'll write a Dockerfile for a web service and define production-ready defaults using these instructions:
Use Build Arguments in a Dockerfile
Sometimes you need to inject information into an image at build time -
a version string, a Git commit hash, or a build configuration flag.
Docker's ARG instruction and --build-arg flag let you parameterize the build
without hardcoding values in the Dockerfile.
In this challenge, you'll use build arguments to inject version and commit metadata into a Go binary during the image build:
Build a Container Image for Another Platform
The machine you build on isn't always the machine you deploy to. When your CI server runs on amd64 but your production fleet is arm64 (or vice versa), you need to build images for a different CPU architecture. Docker can do this using QEMU user-space emulation - it transparently translates instructions from one architecture to another during the build.

In this challenge, you'll build a container image for a non-native platform using QEMU emulation:
Build a Container Image Using Cross-Compilation
QEMU emulation works but can be painfully slow if your application is large. Languages like Go support cross-compilation natively, letting you produce binaries for a different architecture without emulation at all. Combined with multi-stage builds, this approach is both fast and produces minimal images.

In this challenge, you'll use Go's cross-compilation support to build a container image for a non-native platform:
Build a Container Image Using a Remote Builder
When cross-compilation isn't an option - for instance, when your language of choice doesn't support it - you can offload the build to a remote machine that has the right architecture. Docker Buildx lets you register remote builders and transparently send builds to them, so you get native-speed builds without QEMU emulation overhead.
In this challenge, you'll set up a remote builder with Docker Buildx and use it to build a container image for another platform:
How Container Images Actually Work
Loading tutorial...
Before building multi-platform images, it helps to understand what's actually inside them. This tutorial takes you on a deep dive into container image internals - layers, configs, manifests, and image indexes. You'll learn how images are composed, identified by digests, stored in registries, and how multi-platform images use an index to reference platform-specific variants.
Build and Push a Multi-Platform Container Image
Time to bring it all together!
In this capstone challenge, you'll combine QEMU emulation, cross-compilation,
and remote builders to build and push a multi-platform container image
that supports both linux/amd64 and linux/arm64.

This is the real-world workflow for publishing images that run on any architecture: