2024-10-22
7 min read

Docker Image vs Container: Key Differences

Docker Image vs Container: Key Differences

When you first start working with Docker, the relationship between images and containers can seem confusing. You might find yourself running docker images to list containers, or wondering why deleting a container doesn't remove the image. Understanding this distinction is fundamental to working effectively with Docker and will save you hours of debugging later.

Prerequisites

To follow along with this guide, you'll need:

  • Docker installed and running on your system
  • Basic familiarity with command-line operations
  • A text editor for creating Dockerfiles

The Blueprint and Building Analogy

Think of Docker images and containers like architectural blueprints and actual buildings. An image is the blueprint - it contains all the specifications, materials list, and instructions needed to construct something. A container is the actual building created from that blueprint.

Architectural Blueprint    →    Actual Building
      (Image)             →     (Container)

- Floor plans                 - Physical structure
- Material specifications     - Real materials
- Construction steps          - People living/working inside
- Immutable once approved     - Can be modified, decorated
- Can build multiple          - Each building is unique
  identical buildings

Just as you can build multiple identical houses from the same blueprint, you can create multiple containers from the same Docker image.

What Is a Docker Image?

A Docker image is a read-only template that contains everything needed to run an application: the operating system, runtime environment, application code, dependencies, and configuration files. Images are built in layers, with each layer representing a set of changes.

Let's create a simple image to see this in action:

# Create a file called Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "start"]

Build this image and examine its layers:

# Build the image with a tag
docker build -t my-node-app:v1.0 .

# View the image's layers and size
docker history my-node-app:v1.0

You'll see output showing each layer:

IMAGE          CREATED         CREATED BY                                      SIZE
a1b2c3d4e5f6   2 minutes ago   CMD ["npm" "start"]                             0B
b2c3d4e5f6g7   2 minutes ago   EXPOSE 3000                                     0B
c3d4e5f6g7h8   2 minutes ago   COPY . .                                        1.2MB
d4e5f6g7h8i9   3 minutes ago   RUN npm install                                 45MB
e5f6g7h8i9j0   3 minutes ago   COPY package*.json ./                           2KB
f6g7h8i9j0k1   3 minutes ago   WORKDIR /app                                    0B
g7h8i9j0k1l2   2 weeks ago     /bin/sh -c #(nop) CMD ["node"]                  0B

Each line represents a layer in your image. These layers are cached and reused when possible, making subsequent builds faster.

What Is a Docker Container?

A container is a running instance of an image. When you start a container, Docker takes the read-only image and adds a thin writable layer on top where your application can store data, write logs, and make changes during runtime.

Create and run a container from your image:

# Run a container from the image
docker run -d --name my-app-prod -p 3000:3000 my-node-app:v1.0

# View running containers
docker ps

You'll see your container running with a unique container ID and name. This container has its own:

  • Process space
  • Network interface
  • Filesystem (image layers + writable layer)
  • Resource allocation

The Image-Container Relationship in Practice

Here's how the workflow typically looks in real development:

Development Workflow:
┌─────────────┐    ┌──────────────┐    ┌─────────────────┐
│  Dockerfile │───▶│ Docker Image │───▶│ Docker Container│
│             │    │              │    │                 │
│ Instructions│    │ Template/    │    │ Running         │
│ for building│    │ Snapshot     │    │ Instance        │
└─────────────┘    └──────────────┘    └─────────────────┘
                           │                      │
                           │                      │
                   Can create multiple     Each container
                   identical images       has unique state

Let's see this in action by creating multiple containers from the same image:

# Create multiple containers from the same image
docker run -d --name my-app-dev -p 3001:3000 my-node-app:v1.0
docker run -d --name my-app-staging -p 3002:3000 my-node-app:v1.0
docker run -d --name my-app-test -p 3003:3000 my-node-app:v1.0

# List all containers
docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}"

Now you have four containers (including the production one) all running from the same image but operating independently. Each container can have different data, different environment variables, and different runtime state.

Practical Differences in Daily Development

Understanding the image-container distinction becomes crucial in several scenarios:

Debugging Application Issues

When troubleshooting, you need to know whether the issue is in the image (build-time problem) or the container (runtime problem):

# Check image-related issues (build problems, missing files)
docker image inspect my-node-app:v1.0

# Check container-related issues (runtime problems, logs, processes)
docker logs my-app-prod
docker exec -it my-app-prod sh

Storage and Cleanup

Images and containers have different storage implications:

# View image storage
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"

# View container storage (including writable layer)
docker ps -s --format "table {{.Names}}\t{{.Image}}\t{{.Size}}"

When you remove a container, you only delete its writable layer:

# Remove a container - image remains
docker rm my-app-test

# Remove an image - only works if no containers use it
docker rmi my-node-app:v1.0

Sharing and Deployment

Images are portable and can be shared via registries:

# Tag image for sharing
docker tag my-node-app:v1.0 username/my-node-app:v1.0

# Push to registry
docker push username/my-node-app:v1.0

# Pull on another machine
docker pull username/my-node-app:v1.0

# Run containers from the shared image
docker run -d username/my-node-app:v1.0

Common Misconceptions and Troubleshooting

"I deleted the container, why is the image still there?"

This confusion happens because containers and images are stored separately:

# This removes the container but leaves the image
docker rm my-app-prod

# The image is still available for creating new containers
docker images | grep my-node-app

"Why can't I delete this image?"

Docker prevents you from deleting images that containers are using:

# This will fail if containers exist
docker rmi my-node-app:v1.0

# First remove all containers using the image
docker rm $(docker ps -aq --filter ancestor=my-node-app:v1.0)

# Then remove the image
docker rmi my-node-app:v1.0

"My changes disappeared when I restarted the container"

Changes made inside a container's writable layer are lost when the container is removed:

# Changes made here are temporary
docker exec -it my-app-prod sh
echo "temporary data" > /tmp/myfile.txt
exit

# After removing and recreating the container
docker rm my-app-prod
docker run -d --name my-app-prod -p 3000:3000 my-node-app:v1.0

# The file is gone because we're using a fresh container
docker exec -it my-app-prod cat /tmp/myfile.txt  # File not found

To persist data, use volumes:

# Create a container with persistent storage
docker run -d --name my-app-prod -p 3000:3000 -v app-data:/app/data my-node-app:v1.0

Best Practices for Image and Container Management

Keep your images lightweight by using multi-stage builds and appropriate base images:

# Use smaller base images when possible
FROM node:18-alpine  # Much smaller than node:18

# Use multi-stage builds to reduce final image size
FROM node:18 AS builder
COPY package*.json ./
RUN npm ci --only=production

FROM node:18-alpine
COPY --from=builder /app/node_modules ./node_modules
COPY . .
CMD ["npm", "start"]

Tag your images meaningfully:

# Use semantic versioning
docker build -t my-app:1.2.3 .
docker build -t my-app:latest .

# Include environment or purpose in tags
docker build -t my-app:1.2.3-production .
docker build -t my-app:dev .

Name your containers descriptively:

# Clear naming makes management easier
docker run -d --name frontend-prod my-app:latest
docker run -d --name api-staging api:v2.1
docker run -d --name db-development postgres:13

Understanding the image-container relationship is fundamental to working effectively with Docker. Images provide consistency and portability, while containers give you the flexibility to run multiple instances with different configurations. This separation allows you to build once and deploy anywhere, making your applications more reliable and your deployments more predictable.

Published: 2024-10-22|Last updated: 2024-10-22T09:00:00Z

Found an issue?