Docker for Backend Developers: Part 2 - Getting Docker Set Up and Your First Service

Table of contents
- The Mental Model That Changed Everything
- Understanding the Docker Architecture
- Installing Docker: The Foundation
- Your First Container: Understanding the Process
- Building Your First Backend Service Container
- Container Lifecycle: Understanding State
- The Development Workflow Revolution
- Environment Variables: Configuration Theory
- The Container Mindset Shift
- What You've Learned
- Next Steps

After my Docker awakening in Part 1, I was eager to get started. But here's what I wish someone had told me: understanding Docker isn't just about learning commands—it's about shifting how you think about application runtime environments.
Let me walk you through not just the "how" but the "why" behind every step.
The Mental Model That Changed Everything
Before diving into installation, I need to share the mental shift that made Docker click for me.
Traditional Thinking: Applications Live on Servers
For years, I thought like this:
- Server: A machine with an operating system
- Application: Code that runs on that server
- Dependencies: Stuff I install on the server to make my code work
This led to the nightmare of environment differences, dependency conflicts, and "works on my machine" syndrome.
Container Thinking: Applications ARE Their Environment
Docker flipped this completely:
- Container: A complete runtime environment
- Application: Code plus everything it needs to run
- Host: Just provides the kernel and resources
The profound realization: You're not running applications on a server—you're running self-contained environments that happen to contain your application.
Understanding the Docker Architecture
Before we install anything, let's understand what Docker actually is under the hood.
The Three-Layer Reality
Layer 1: Linux Kernel Features
- Namespaces: Process isolation (your container thinks it's alone)
- Cgroups: Resource limiting (preventing containers from hogging resources)
- Union File Systems: Layered storage (efficient image management)
Layer 2: Container Runtime
- containerd: Actually runs containers
- runc: Low-level container execution
Layer 3: Docker Engine
- Docker Daemon: Manages images and containers
- Docker CLI: The commands you type
- Docker API: How other tools talk to Docker
Why This Matters for Backend Developers
Understanding this stack taught me why containers are so powerful:
- Isolation without VMs: Namespaces give you process isolation without the overhead of full virtualization
- Resource Control: Cgroups ensure your runaway Python process doesn't kill the entire server
- Efficient Storage: Union filesystems mean you can have 100 containers sharing the same base Ubuntu image
Installing Docker: The Foundation
I learned this the hard way: how you install Docker matters. Let me show you the right way and explain why.
Why the Official Installation Method Matters
There are multiple ways to install Docker, but only one gives you the latest features, security updates, and proper integration:
# The wrong way (what I did first)
sudo apt install docker.io # This gives you an old, community-maintained version
# The right way
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
The get-docker.sh
script handles the complexity of:
- Adding Docker's official repository
- Installing the correct version for your OS
- Setting up proper systemd integration
- Configuring the Docker daemon
The Post-Installation Ritual
After installation, there's a crucial step most tutorials gloss over:
# Add yourself to the docker group
sudo usermod -aG docker $USER
# Log out and back in (or run this)
newgrp docker
# Test it works without sudo
docker run hello-world
Why this matters: Without this, you'll need sudo
for every Docker command. Not only is this annoying, but it can cause permission issues with volumes and file ownership.
Your First Container: Understanding the Process
Let's run your first container, but I want you to understand what's happening at each step.
The Hello World Moment
docker run hello-world
Here's what just happened (the magic I wish someone had explained):
- Image Resolution: Docker looks for
hello-world:latest
locally - Registry Pull: Not found locally, so it downloads from Docker Hub
- Container Creation: Creates a new container from the image
- Namespace Setup: Allocates process, network, and filesystem namespaces
- Process Execution: Runs the default command inside the isolated environment
- Cleanup: Container exits and stops (but isn't deleted)
The Layers Revealed
Let's examine what we just downloaded:
docker images hello-world
docker history hello-world
You'll see the hello-world
image is tiny (less than 20KB) and consists of just a few layers. This illustrates Docker's layered filesystem concept—each instruction in a Dockerfile creates a new layer.
Building Your First Backend Service Container
Now let's containerize a real backend service and understand the theory behind each step.
The Service: A Simple HTTP Health Check
// main.go
package main
import (
"encoding/json"
"net/http"
"os"
"time"
)
type HealthResponse struct {
Status string `json:"status"`
Timestamp time.Time `json:"timestamp"`
Hostname string `json:"hostname"`
}
func healthHandler(w http.ResponseWriter, r *http.Request) {
hostname, _ := os.Hostname()
response := HealthResponse{
Status: "healthy",
Timestamp: time.Now(),
Hostname: hostname,
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
func main() {
http.HandleFunc("/health", healthHandler)
http.ListenAndServe(":8080", nil)
}
The Dockerfile: Your Environment Definition
# Start with a base environment
FROM golang:1.21-alpine
# Set working directory
WORKDIR /app
# Copy source code
COPY . .
# Build the application
RUN go build -o main .
# Define the default command
CMD ["./main"]
Understanding Each Dockerfile Instruction
Let me explain the theory behind each line:
FROM golang:1.21-alpine
- Theory: Every container starts with a base image
- Practice: We're inheriting a complete Go development environment
- Why Alpine: Minimal Linux distribution (5MB vs 100MB+ for full distributions)
WORKDIR /app
- Theory: Establishes the working directory inside the container
- Practice: Like
cd /app
but permanent for all subsequent commands - Why: Prevents file chaos and ensures consistent paths
COPY . .
- Theory: Transfers files from build context to container filesystem
- Practice: Copies everything in current directory to
/app
in container - Why: The container needs your source code to run
RUN go build -o main .
- Theory: Executes commands during image build time
- Practice: Compiles your Go code into a binary
- Why: Transform source code into executable artifact
CMD ["./main"]
- Theory: Default command when container starts
- Practice: Runs your compiled binary
- Why: Defines what the container actually does
Building and Understanding the Result
# Build the image
docker build -t my-health-service .
# Examine what we created
docker images my-health-service
docker history my-health-service
The docker history
command shows you each layer and its size. This teaches you about image optimization—which layers are taking up space and why.
Running and Examining Your Service
# Run the container
docker run -p 8080:8080 my-health-service
# In another terminal, test it
curl http://localhost:8080/health
The port mapping theory: -p 8080:8080
creates a mapping from host port 8080 to container port 8080. The container has its own network namespace, so without this mapping, you couldn't reach the service.
Container Lifecycle: Understanding State
One concept that confused me initially was container lifecycle. Let me clarify:
Container States
Created: Container exists but isn't running
Running: Container is actively executing
Paused: Container is paused (rare in practice)
Stopped: Container finished executing
Deleted: Container removed from system
Working with Container State
# Run container in background
docker run -d -p 8080:8080 --name health-service my-health-service
# Check running containers
docker ps
# Check all containers (including stopped)
docker ps -a
# Stop the container
docker stop health-service
# Start it again
docker start health-service
# Remove the container
docker rm health-service
Key insight: Images are templates, containers are instances. You can create multiple containers from the same image.
The Development Workflow Revolution
Here's where Docker fundamentally changed my development process:
Before Docker
- Install language runtime on my machine
- Install dependencies globally or in virtual environments
- Configure database connections
- Hope production environment matches
After Docker
- Define environment in Dockerfile
- Build container image
- Run container anywhere
- Production environment IS the container
Volume Mounting: The Development Bridge
For active development, you don't want to rebuild the container every time you change code:
# Mount your source directory into the container
docker run -p 8080:8080 -v $(pwd):/app my-health-service
The theory: This creates a bind mount between your host directory and the container's /app
directory. Changes to files on your host immediately appear in the container.
Environment Variables: Configuration Theory
Backend services need configuration, and containers handle this through environment variables:
# Run with custom configuration
docker run -p 8080:8080 -e LOG_LEVEL=debug my-health-service
Why environment variables: They follow the Twelve-Factor App principle of configuration through environment. This enables the same container image to work in development, staging, and production with different configurations.
The Container Mindset Shift
After building your first container, you should feel a fundamental shift in thinking:
Old mindset: "I need to install Go on this server to run my application" New mindset: "I need to run my Go application container on this server"
This shift is profound because:
- Dependencies are bundled: No more "works on my machine"
- Environment is code: Your Dockerfile defines the exact environment
- Reproducible builds: Same Dockerfile produces identical environments
- Portable deployment: Container runs anywhere Docker runs
What You've Learned
You now understand:
- Docker's architecture and why it works
- Container isolation through namespaces and cgroups
- Image layers and the filesystem
- Container lifecycle and state management
- Environment configuration through variables
- Development workflow with bind mounts
More importantly, you've experienced the mindset shift from "applications on servers" to "applications as environments."
Next Steps
You might be thinking: "This is great for a single service, but what about real applications with databases, caches, and multiple services?"
That's exactly what Part 3 covers—building production-ready containers that handle real-world complexity.
Ready to build containers that can handle production workloads? Part 3 covers production-ready container design.
Subscribe to my newsletter
Read articles from Hrithik Dhakrey directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
