Introduction to Docker: Building Your First Container Image
Stop saying "it works on my machine." Learn how to use Docker to create reproducible environments and build your first container image today.
Previously in this course, we mastered running tests in CI and ensured our pipeline triggers correctly on every push. While we now have automated feedback, we still rely on the host machine's environment—the specific version of Python, system libraries, and OS packages installed on the GitHub Runner.
This is where Docker changes the game. By introducing containerization, we decouple our application from the host environment, achieving true environment parity—the guarantee that the code running on your laptop is exactly what runs in production.
Why Containerization Matters
In traditional development, you might install Python 3.10 on your Mac, while your CI runner uses Ubuntu 22.04 with Python 3.8. These subtle differences lead to the infamous "it works on my machine" syndrome.
Docker solves this by packaging your application, its dependencies, and its runtime into a single, immutable unit called an image. When you run this image, it becomes a container—a process isolated from the host OS, ensuring that your app sees the exact same file system and libraries regardless of where it executes.
Anatomy of a Dockerfile
A Dockerfile is a text document containing the instructions to build an image. Think of it as a recipe.
Create a file named Dockerfile (no extension) in the root of your project:
Dockerfile# 1. Start from a base image FROM python:3.11-slim # 2. Set the working directory inside the container WORKDIR /app # 3. Copy local files into the container COPY . . # 4. Install dependencies RUN pip install --no-cache-dir -r requirements.txt # 5. Define the command to run the app CMD ["python", "app.py"]
Breaking down the instructions:
- FROM: Sets the foundation. We use
python:3.11-slimbecause it’s a lightweight version of Debian with Python pre-installed. - WORKDIR: Creates a directory inside the container. All subsequent commands happen here.
- COPY: Moves files from your local machine into the container's virtual file system.
- RUN: Executes commands during the build process (like installing packages).
- CMD: Specifies what the container should do when it starts.
Hands-on Exercise: Build Your First Image
Ensure you have Docker Desktop installed. Open your terminal in your project root and execute the following:
-
Build the image:
docker build -t my-app .The-tflag tags your image with a name (e.g.,my-app). The.tells Docker to look for theDockerfilein the current directory. -
Verify the build: Run
docker images. You should seemy-applisted in the output. -
Run the container:
docker run --rm my-appThe--rmflag automatically removes the container once it stops, keeping your machine clean.
Common Pitfalls
- Ignoring the
.dockerignorefile: Just like.gitignore, you should create a.dockerignorefile. Without it, you might accidentally copy your localvenv/or.git/folder into the image, making it unnecessarily huge. - Running as root: By default, containers run as the
rootuser. For production-grade security, you should create a non-privileged user inside yourDockerfile. - Layer Bloat: Each instruction (
RUN,COPY) creates a layer. If you change a file early in the file, every layer after it must be rebuilt. Order your instructions from least-to-most frequently changed (e.g., copyrequirements.txtand install dependencies before copying your source code).
FAQ
Q: Is a container a Virtual Machine (VM)? A: No. VMs include a full guest operating system (heavy). Containers share the host's kernel, making them significantly faster and lighter.
Q: Can I use Docker for non-Python apps? A: Absolutely. Whether it's Node.js, Go, or Rust, the principle of packaging the runtime and dependencies remains the same.
Q: How do I know which base image to choose?
A: Start with the official "slim" version of your language (e.g., node:18-slim or python:3.11-slim). They offer the best balance between size and utility.
Recap
We’ve moved beyond simple script execution to true environment packaging. By creating a Dockerfile and building a local image, you have ensured that your code's environment is now version-controlled and reproducible. This foundational step is critical for containerization basics and sets us up to move our CI environment into the cloud.
Up next: Dockerizing the Pipeline — we'll take this local image and teach GitHub Actions how to use it for our automated testing.
Work with me

CI/CD Pipeline & Docker Containerization
Ship with confidence: automated CI/CD pipelines and Docker setups so every push is tested and deployed — no more manual, error-prone releases.

Next.js E-commerce Store Development
Turn your Facebook page or small shop into a real online store — fast, mobile-first, and built to sell. Own your storefront, not just a social page.
