Build Container Images Like a Pro
Skill Path ( EasyMedium )

Building container images can be both an easy and a hard problem. Building just an image is usually as straightforward as replicating your local app build steps in a Dockerfile's RUN instructions. Building a small, maintainable, and secure image, on the other hand, may involve lots of considerations, elaborate techniques, and domain knowledge.

Container image composition and the main factors that affect it.

This learning path will take you from creating and pushing your very first container image, to analyzing the contents of the image, to picking an optimal base, and learning how to spot and troubleshoot runtime problems caused by flawed image composition. All of this is packed with engaging diagrams and hands-on exercises.

Happy building!

Level up your Server Side game — Join 9,000 engineers who receive insightful learning materials straight to their inbox

Warming up: Docker Image Building 101

To get started, let's build and push your very first container image:

Know what you've built

Loading tutorial...

One of the critical but often overlooked skills when working with container images is the ability to introspect their contents and know exactly what's inside. Yes, images are often represented as a sequence of overlaid folders, but when mounted by the container runtime, the result looks like a regular directory, which often contains a typical Linux distro root filesystem (the rootfs of the future container).

In this tutorial, you'll learn about different ways to extract a container image filesystem using standard Docker commands (spoiler: it's not that simple if you want to inspect the truly unmodified original rootfs).

Practice time: Extract container image filesystem

Check your image inspection skills by solving this slightly more complex challenge:

Choosing an optimal base

Loading tutorial...

Now that you're familiar with the main docker build mechanics, it's time to tackle probably the most important topic: how to pick an optimal base image for your application.

This tutorial explores various Node.js base image options and serves as an example of how to do proper due diligence when choosing a base image. While the tutorial is focused on Node.js images, it touches on an important image composition problem that reoccurs in many other container images, such as python, ruby, or even rust - and to a smaller extent, golang.

Multi-stage builds

Loading tutorial...

Choosing the right base container image can be particularly tricky if you want to both build and run your application in Docker. The tools (e.g., compilers and linters) and packages (e.g., testing frameworks, code generators) needed to build a typical application differ significantly from what's required to run it in production.

For a production image to be efficient and secure, it must contain only an absolutely minimal set of OS-level packages and runtime dependencies, whereas compiler toolchains tend to be huge and riddled with CVEs (but absolutely necessary for building the application).

Luckily, with multi-stage builds, you don't need to pick a single base image to satisfy both build and runtime requirements. Learn more about this vital technique here.

Practice time: Containerizing Node.js applications

Container images are often poorly constructed because Dockerfiles are treated as no man's land:

  • Application developers may lack the motivation and skills to write optimal Dockerfiles.
  • DevOps engineers may lack deep knowledge of the application stack and current best practices.

This disconnect is understandable - it's a lot of cross-functional knowledge to juggle! To solve these Node.js challenges, you might need to consult framework documentation on producing standalone builds. Ah, and don't forget to use a multi-stage build to keep the image small and secure:

Containerizing Node.js applications: Part II

Solve this similar challenge to understand which "best practices" carry over between different stacks - and which don't:

Containerizing Node.js applications: Part III

While Next.js and Nuxt support "standalone" builds, Svelte is slightly different. Its project dependencies must be copied from the build stage to the production stage, along with the app bundle itself.

While doing this, it's crucial to avoid copying dev-time dependencies by mistake. This challenge highlights the importance of truly understanding stack-specific nuances in addition to Docker best build practices:

What does the smallest possible base image look like?

Loading tutorial...

Building container images FROM scratch to keep them as small (and secure) as possible is a common approach - especially for compiled languages like Go or Rust. While it works in simple cases, more complex applications can run into issues due to missing runtime components.

In this tutorial, you'll learn about the pitfalls of building images FROM scratch and typical application expectations for execution environments.

Practice time: Dockerize a statically linked Go application

Apply the knowledge from the previous tutorial to containerize this simple Go app:

Troubleshoot a containerized Go application

Solve this challenge to understand how missing components in a FROM scratch image can cause runtime problems:

Distroless as a better runtime base

Loading tutorial...

If FROM scratch failures are common, is there a better way to build minimal container images? Enter distroless images, often called "better scratch." They have (almost) no CVEs and include essential components like a proper rootfs layout, CA certificates, and timezone info.

Learn more in this highly illustrated and hands-on tutorial.

Practice time: Dockerize a dynamically linked Go application

Use your newfound knowledge to containerize this more complex Go app:

Troubleshoot another containerized Go application

Another service failing due to a bad containerization attempt - can you fix it? Apply your knowledge of distroless images to rebuild the container:

Ideal image rootfs isn't everything

A flawed Dockerfile can cause more than just missing files or packages. Solve this challenge to explore another common issue - broken signal propagation:

Know your application stack

The same issue (broken graceful termination) can arise from different root causes. This challenge highlights how a lack of knowledge about your app stack or Linux basics (e.g., sub-shells and signal propagation) can lead to production issues:

Container Image Security 101

Last but not least! Poor image composition (unnecessary dependencies) or lack of proper maintenance (missing security patches) can lead to extra work for your security team - or worse, production breaches.

Learn how to scan your images for vulnerabilities and patch affected packages in this challenge by a long-time Docker engineer Felipe Cruz:

Level up your Server Side game — Join 9,000 engineers who receive insightful learning materials straight to their inbox