A Beginner‘s Guide to Docker: How to Create Your First Application

As a seasoned full-stack developer, I‘ve seen firsthand how Docker has revolutionized the way we build, ship, and run applications. In this comprehensive beginner‘s guide, I‘ll introduce you to the core concepts of Docker and walk you through containerizing your first app.

Whether you‘re a junior developer just starting out or a veteran looking to modernize your workflow, this guide will give you a solid foundation in one of the most important technologies in modern software development. Let‘s get started!

What is Docker?

Docker is an open platform for developing, shipping, and running applications in lightweight, portable containers. Since its initial release in 2013, Docker has seen explosive growth in adoption and has become the de facto standard for containerization.

To give you an idea of Docker‘s popularity, consider these statistics:

  • Over 13 billion Docker image pulls as of 2020 (source)
  • 79% of companies have adopted Docker in production (source)
  • The container market is projected to reach $4.3 billion by 2022 (source)

So what exactly is a container? In simple terms, a container is a unit of software that packages code and its dependencies so the application runs quickly and reliably across different computing environments.

Containers have actually been around for decades in various forms, like FreeBSD jails and AIX workload partitions. But Docker made containerization accessible to the masses by providing a simple, standard interface for creating and managing containers.

Docker vs Virtual Machines

If you‘re familiar with virtual machines (VMs), you might be wondering how containers differ. While both provide isolation for applications, there are some key differences:

Feature Containers Virtual Machines
Virtualization App-level Hardware-level
Guest OS Not required Required for each VM
Size Lightweight (MBs) Heavy (GBs)
Startup time Seconds Minutes
Resource efficiency High Low

As you can see, containers are much more lightweight and efficient than VMs. They share the host machine‘s OS kernel and don‘t require a full OS instance for each app. This allows you to run many containers on a single host, making better use of resources.

Why Use Docker?

Now that you know what Docker is, let‘s look at some of the key benefits it provides.

Consistency Across Environments

One of Docker‘s main selling points is the ability to "build once, run anywhere." By packaging an app and its dependencies into a container, you can be sure it will run the same regardless of the environment – whether that‘s development, staging, production, or a coworker‘s laptop.

This consistency eliminates the age-old problem of "works on my machine" bugs caused by subtle differences in runtimes, libraries, and configurations.

Rapid Deployment

Containerizing an application also streamlines the deployment process. Instead of manually configuring a host with the right dependencies, you simply deploy the Docker image to any host running Docker.

This portability enables rapid, predictable deployments and makes it easy to scale an application across a cluster of machines.

Isolation and Security

Containers provide a level of isolation between applications running on the same host. Even though they share the underlying OS kernel, each container runs in its own isolated process space and has limited access to the host.

This isolation enhances security by minimizing the "blast radius" if an application is compromised. It also prevents conflicts between applications that have different or incompatible dependencies.

Efficiency and Density

As mentioned before, containers have a much smaller footprint than VMs. Not only do container images take up less disk space, but a single host can also support many more containers than equivalent VMs.

This efficiency translates to lower infrastructure costs and allows you to make the most of your computing resources. It‘s particularly valuable in large-scale, high-density environments like cloud platforms and microservices architectures.

Containerizing Your First Docker Application

With that background in mind, let‘s dive into creating your first Docker application! For this tutorial, we‘ll create a simple Python script and package it into a Docker image.

Prerequisites

To follow along, you‘ll need:

  • Docker installed on your machine (installation guide)
  • Basic familiarity with the command line
  • A code editor of your choice

Step 1: Create the Application

First, let‘s create a simple Python script that will be the basis of our Docker image. Create a new directory for the project and add a file named app.py with the following contents:

import os
import datetime

name = os.getenv("NAME", "world")
now = datetime.datetime.now()

print(f"Hello, {name}!")
print(f"Timestamp: {now}")

This script does a couple things:

  1. Reads an environment variable called NAME and defaults to "world" if it‘s not set
  2. Gets the current timestamp
  3. Prints a hello message with the NAME value and current timestamp

Step 2: Create the Dockerfile

Next, we need to create a Dockerfile that describes how to package the script into a Docker image. In the same directory as app.py, create a file named Dockerfile with these contents:

# Use an official Python runtime as the base image
FROM python:3.9

# Set the working directory in the container
WORKDIR /app

# Copy the current directory contents into the container
COPY . /app

# Run the command to start the app
CMD ["python", "app.py"]

Let‘s break this down:

  • FROM python:3.9: This sets the base image for our Docker image to the official Python 3.9 image. Using an official image saves us from having to install Python ourselves.

  • WORKDIR /app: This sets the working directory inside the container to /app. Subsequent commands will be executed relative to this directory.

  • COPY . /app: This copies the contents of the current directory (denoted by .) on the host machine into the /app directory inside the container.

  • CMD ["python", "app.py"]: This specifies the default command to run when a container is started from this image. It runs our Python script using the python executable.

Step 3: Build the Docker Image

With the Dockerfile written, we can now build the actual Docker image. Open a terminal, navigate to the directory containing the Dockerfile, and run:

docker build -t python-hello-world .

This command tells Docker to build an image based on the Dockerfile in the current directory (denoted by .). The -t flag tags the image with a friendly name, in this case "python-hello-world".

As the image builds, you‘ll see output showing each instruction being executed:

[+] Building 2.7s (8/8) FINISHED                                                                                                                                                                                                                               
 => [internal] load build definition from Dockerfile                                                                                                                                                                                                      0.0s
 => => transferring dockerfile: 31B                                                                                                                                                                                                                       0.0s
 => [internal] load .dockerignore                                                                                                                                                                                                                         0.0s
 => => transferring context: 2B                                                                                                                                                                                                                           0.0s
 => [internal] load metadata for docker.io/library/python:3.9                                                                                                                                                                                             2.4s
 => [1/4] FROM docker.io/library/python:3.9@sha256:c4b7d9f6e66f4b0389cec05929cc5e89f1a5d12c3f72c6ed8763d6cb8e1c8630                                                                                                                                     0.0s
 => [internal] load build context                                                                                                                                                                                                                         0.0s
 => => transferring context: 228B                                                                                                                                                                                                                         0.0s
 => CACHED [2/4] WORKDIR /app                                                                                                                                                                                                                             0.0s
 => [3/4] COPY . /app                                                                                                                                                                                                                                     0.0s 
 => exporting to image                                                                                                                                                                                                                                    0.1s
 => => exporting layers                                                                                                                                                                                                                                   0.0s
 => => writing image sha256:8cae92a8fbd6d091ce687b71b31252056944b09760438905b726625831564c4e                                                                                                                                                              0.0s
 => => naming to docker.io/library/python-hello-world                                                                                                                                                                                                     0.0s

Once the build completes, you can verify the new image exists by running:

docker images

You should see the python-hello-world image listed in the output.

Step 4: Run the Docker Container

Now for the exciting part – let‘s run a container based on our newly-minted Docker image! To do so, use the docker run command:

docker run python-hello-world

This tells Docker to start a new container based on the python-hello-world image. Since we didn‘t specify any additional options, Docker will run the default command defined in the Dockerfile, which is python app.py.

You should see output like this:

Hello, world!
Timestamp: 2021-08-20 14:32:07.360567

The container prints the hello message and current timestamp, then exits because the script finished.

Congratulations – you just built and ran your first Docker container!

Diving Deeper into Dockerfiles

In the simple example above, we used a bare-bones Dockerfile to package our Python script. But Dockerfiles support many other instructions for customizing your image. Here are a few key ones to know:

  • ENV: Set environment variables
  • RUN: Execute commands during the image build
  • EXPOSE: Expose ports to the host machine
  • VOLUME: Create a mount point for persistent or shared data

For example, we could modify our Dockerfile to set the NAME environment variable and expose port 8000:

FROM python:3.9

ENV NAME="John"  

WORKDIR /app

COPY . /app

EXPOSE 8000

CMD ["python", "app.py"]

By setting NAME in the Dockerfile, we provide a default value that can still be overridden at runtime using docker run -e.

It‘s important to keep your Dockerfiles minimal and focused. Each instruction adds a new layer to the image, increasing its size. Follow these best practices to keep your images lean:

  • Use official, minimal base images like python:3.9-slim
  • Combine multiple RUN instructions into one to reduce the number of layers
  • Only copy necessary files into the image
  • Avoid installing unnecessary packages

Using Docker for Development

So far we‘ve focused on packaging and running applications with Docker. But Docker is also a powerful tool for standardizing development environments.

By defining your application‘s runtime and dependencies in a Dockerfile, you make it easy for any developer to spin up a consistent development environment. No more wasting hours setting up tools and chasing down conflicting versions!

To streamline your development workflow with Docker, you can use volumes to mount your local source code into a container. This allows you to make changes to the code on your host machine and see them reflected instantly in the running container.

For example, to mount the current directory into a Python container and drop into an interactive shell, you could run:

docker run -v ${PWD}:/app -it python:3.9 /bin/bash

This command starts a new container from the python:3.9 image, mounts the current host directory to /app inside the container, and runs an interactive Bash shell.

You can also use Docker Compose to define your development environment as code and manage multiple services. With a simple YAML file, you can specify your application‘s services, networks, and volumes, and bring them up or down with a single command.

Here‘s an example docker-compose.yml file for a simple web application:

version: ‘3‘
services:
  web:
    build: .
    ports:
      - "8000:8000"
    volumes:
      - .:/app
  db:
    image: postgres
    environment:
      POSTGRES_PASSWORD: example

This file defines two services: a web service built from the current directory‘s Dockerfile, and a PostgreSQL database service using an official image. Running docker-compose up will start both services and link them together on a common network.

Conclusion

In this comprehensive guide, we‘ve covered the basics of Docker and walked through containerizing your first application. We‘ve explored key Docker concepts like images and containers, examined the anatomy of a Dockerfile, and highlighted Docker‘s benefits for development and production alike.

But this is just the tip of the iceberg. The Docker ecosystem is vast and constantly evolving, with a plethora of tools and services for building, shipping, and running containers at scale. I encourage you to dive deeper into topics like:

  • Multi-stage builds for optimizing Dockerfiles
  • Docker registries for distributing images
  • Container orchestration with Kubernetes or Docker Swarm
  • Serverless platforms like AWS Fargate or Google Cloud Run

As a full-stack developer, I‘ve found Docker to be an indispensable tool for streamlining my workflow and deploying applications with confidence. By adopting Docker and containers, you‘ll be well-equipped to tackle the challenges of modern software development.

I hope this guide has given you a solid foundation in Docker and inspired you to containerize your own applications. Feel free to reach out if you have any questions or want to share your experiences with Docker. Happy containerizing!

Additional Resources

Similar Posts