Skip to main content
CleanStart

Knowledge Hub

Images and Containers: How Static Artifacts Become Running Processes

Watch the Lesson

You have a container image. You run it. Now you have a running container. What's the relationship? Is an image just a pre-compressed container? Can you have 100 containers from one image? What happens when a container modifies a file? Does it change the image? This guide explains the relationship between images and containers — how static artifacts become processes, how multiple containers from one image remain independent, and what happens to data when containers stop.

Table of Contents

  1. The Relationship: Blueprint and Instance
  2. What Happens When You Run docker run
  3. The Container Lifecycle
  4. The Writable Container Layer
  5. Multiple Containers from One Image
  6. Container State: What Persists, What Doesn't
  7. Immutable Images, Mutable Containers
  8. Container IDs, Names, and Runtime Tracking
  9. Snapshots and Copying Container State
  10. Next Steps

The Relationship: Blueprint and Instance

An image is a static artifact — a stack of filesystem layers and metadata. It's immutable. Once built and pushed to a registry, it never changes.

A container is a running (or stopped) instance of an image — a process with an isolated filesystem, network, and resource limits. It can be created, started, stopped, and deleted.

The relationship:

graph TB    Image["Image<br/>Static<br/>Immutable<br/>sha256:abc123"]    Image -->|docker run| C1["Container 1<br/>Running<br/>Writable layer"]    Image -->|docker run| C2["Container 2<br/>Running<br/>Writable layer"]    Image -->|docker run| C3["Container 3<br/>Stopped<br/>Preserved state"]    C1 -->|Write file| W1["Writable<br/>Container Layer 1"]    C2 -->|Write file| W2["Writable<br/>Container Layer 2"]    C3 -->|Write file| W3["Writable<br/>Container Layer 3"]    style Image fill:#fff9c4    style C1 fill:#c8e6c9    style C2 fill:#c8e6c9    style C3 fill:#f0f4c3

An image is a static blueprint consisting of Layer 1 (Base OS), Layer 2 (Dependencies), Layer 3 (Application), and Config metadata (entrypoint, environment variables, working directory).

When you execute docker run to instantiate the image, you create containers. Container 1 (a running instance) includes the read-only layers from the image, a writable container layer unique to this instance, and a running Process (PID 1). Container 2 (another instance of the same image) has the same read-only layers (shared with Container 1), its own unique writable container layer, and its own running Process. Container 3 (a stopped instance) has the shared read-only layers, a persisted writable container layer on disk, and a stopped Process.

You can have unlimited containers from one image. Each is independent. **Analogy**: An image is like a class definition in programming. A container is like an instance of that class. You can instantiate a class 100 times; each instance is independent. --- ## What Happens When You Run docker run When you execute `docker run -d -e DEBUG=true myapp:1.0`, here's the exact sequence: ### Step 1: Resolve Image ```bashdocker run myapp:1.0

Docker checks:

  1. Is myapp:1.0 in local image cache? If yes, use it.
  2. If not, pull from registry (specified or default Docker Hub)
  3. Verify image digest (sha256) matches manifest

Result: Image is available locally at /var/lib/docker/image/overlay2/... (exact path depends on storage driver).

Step 2: Create Container

When a container is created, Docker stacks image layers (which are read-only) containing system binaries like /bin/sh, system libraries in /lib/, system utilities in /usr/, and application code like /app/app.py using OverlayFS. This union filesystem presents all these layers as a unified filesystem to the container, so everything appears normal inside the container even though the underlying layers are read-only, with only a top writable layer allowing modifications.

Docker creates a container layer (writable) on top of the image layers (read-only) using OverlayFS.

Docker also creates:

  • Namespace directory: For process, network, IPC, UTS isolation
  • cgroup directory: For resource limits
  • Container metadata: Configuration, state, logs

Step 3: Configure Container

Docker reads the image's config blob and applies settings:

  • Environment variables: -e DEBUG=true + image-defined ENV vars
  • Working directory: WORKDIR from image
  • Entrypoint and Cmd: How to start the process
  • User: Who runs the process (root by default)
  • Volumes: Mount paths

Step 4: Start Container

Docker calls the container runtime (containerd or runc) with the config.json file:

  1. Create namespaces (PID, network, filesystem, etc)
  2. Set up cgroups (CPU, memory limits)
  3. Mount filesystem (stacked image layers + container layer)
  4. Set environment variables
  5. Change working directory
  6. Drop capabilities (if specified)
  7. Fork a new process (PID 1 inside container)
  8. Exec the entrypoint

Result: Process is running inside the container, isolated.

Step 5: Return Container ID

Docker outputs the container ID (sha256 hash of container config) and returns.

Example:

$ docker run -d myapp:1.0a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6  ← Container ID $ docker psCONTAINER ID    IMAGE        STATUSa1b2c3d4        myapp:1.0    Up 2 minutes

The container ID uniquely identifies this container. The image ID is separate.

The Container Lifecycle

A container goes through states:

A container progresses through states: created (initialized but not running), running (process executing), paused (suspended), stopped (process exited), and deleted (removed). The container layer persists throughout these state transitions, allowing containers to be stopped and restarted while retaining their data.

State Transitions

Created (not started):

docker create myapp:1.0

Container is set up but not running. Container layer exists (empty). Useful for inspecting the filesystem before starting.

Running (process is active):

docker start <container># ordocker run <image>

Process is executing. Container layer accumulates changes (files written, modified).

Paused (frozen):

docker pause <container>docker unpause <container>

Process is suspended. Useful for checkpoint/restore or freezing state without stopping.

Stopped (process exited):

docker stop <container># or process exits naturally

Process is no longer running, but container layer is preserved. You can restart it.

Deleted (removed):

docker rm <container>

Container layer is deleted. Cannot restart.

Container Persistence

When a container stops, does it persist?

$ docker run myapp:1.0# Container runs and exits# Container still exists! $ docker ps -aCONTAINER ID    IMAGE        STATUSa1b2c3d4        myapp:1.0    Exited (0) 2 minutes ago $ docker restart a1b2c3d4# Container starts again from where it left off

The container layer is preserved even after the process stops. You can restart it, inspect its filesystem, or commit it to a new image.

The Writable Container Layer

Every running (or stopped) container has a writable layer on top of the image's read-only layers.

How Writes Work

When a process inside a container writes a file:

Process: echo "hello" > /app/output.txt OverlayFS behavior:1. Check lower layers (image layers): /app/output.txt doesn't exist2. Write to upper layer (container layer): /app/output.txt ← written here3. Next read of /app/output.txt: Found in upper layer, return it Result: File exists in container layer, not in image layers

If the image already has a file:

Image has: /etc/config.yaml (from base OS) Process: echo "newval" > /etc/config.yaml OverlayFS behavior:1. Check upper layer (container): /etc/config.yaml doesn't exist2. Check lower layers (image): /etc/config.yaml exists3. OverlayFS copies the file to upper layer (copy-on-write)4. Process modifies the copy in upper layer5. Next read: Found in upper layer, return it (modified version) Result: Image's /etc/config.yaml is unchanged        Container has its own /etc/config.yaml

Copy-on-Write Efficiency

This mechanism (copy-on-write) is why containers from the same image don't interfere:

Image layers (1 GB total):  Layer 1: Base OS (800 MB)  Layer 2: Dependencies (200 MB) Container A:  Write 1 file (100 KB) → container layer A Container B:  Write 1 file (100 KB) → container layer B Storage used:  Image: 1 GB (shared)  Container A: 100 KB (unique)  Container B: 100 KB (unique)  Total: 1.0002 GB Without copy-on-write, it would be:  Total: 1 GB + 1 GB + 1 GB = 3 GB

Multiple Containers from One Image

You can create as many containers as you want from one image:

docker run -d --name web-1 myapp:1.0docker run -d --name web-2 myapp:1.0docker run -d --name web-3 myapp:1.0

Now you have 3 containers, all from the same image:

A single image myapp:1.0 (1 GB total size) can support multiple containers simultaneously: Container web-1 running with an additional 50 MB in its container layer, Container web-2 running with 40 MB in its layer, and Container web-3 running with 60 MB in its layer. The total disk space required is just 1.15 GB (the 1 GB base image plus the per-container layers), demonstrating the efficiency of layer sharing across containers.

Key points:

  1. Shared layers: All 3 containers share the 1 GB image
  2. Independent container layers: Each container has its own writable layer
  3. Isolation: Changes in web-1's container layer don't affect web-2 or web-3
  4. Scalability: You can run 100 containers from one image with ~5-10 GB total disk

This is why containers are efficient. VMs can't share kernels or base layers; each VM needs 500+ MB just for the OS.

Container State: What Persists, What Doesn't

What Persists (Saved in Container Layer)

Files written by the process:

$ docker run ubuntu:22.04 sh -c "echo hello > /tmp/file.txt"# /tmp/file.txt is in the container layer# When container stops, it persists $ docker exec <container> cat /tmp/file.txthello  ← File is still there even after container stopped

Filesystem modifications:

$ docker run ubuntu:22.04 sh -c "mkdir /newdir && touch /newdir/file"# /newdir and /newdir/file are in the container layer $ docker start <container>$ docker exec <container> ls /newdirfile  ← Directory persists

Database writes (if your app writes to local disk):

$ docker run sqlite:latest sh -c "sqlite3 /app/data.db 'INSERT INTO users VALUES (...)'"# /app/data.db changes are in the container layer# Persist across stop/start

What Doesn't Persist (Lost When Container Deleted)

Everything in the container layer:

$ docker run ubuntu:22.04 sh -c "echo secret > /tmp/file.txt"$ docker rm <container>  ← Deletes the container layer$ docker exec <container> cat /tmp/file.txt  ← Error: container doesn't exist# File is lost

RAM (ephemeral): Processes write to RAM; it's lost when process stops. Only data written to disk persists.

Network changes: iptables, routing, network configuration — lost when container stops.

Immutable Images, Mutable Containers

This is a critical design principle:

Images are immutable:

docker run ubuntu:22.04 sh -c "echo modified > /bin/bash"# You're not modifying the image's /bin/bash# You're writing to the container layer (copy-on-write)# The image's /bin/bash is unchanged docker run ubuntu:22.04 cat /bin/bash# Returns original /bin/bash from image# Your modification is only in that container's layer

Containers are mutable:

# Container 1docker run -d --name c1 ubuntu:22.04 sleep 1000docker exec c1 sh -c "echo data > /app/state.txt" # Container 1 now has /app/state.txt in its layer# Container 2docker run -d --name c2 ubuntu:22.04 sleep 1000docker exec c2 sh -c "cat /app/state.txt"  ← Error: file doesn't exist# Container 2 doesn't see c1's files

Why This Matters

Immutability of images:

  • Same image produces same behavior (reproducibility)
  • You can trust the image hasn't changed since you pulled it
  • Easy to rollback: run an old image version
  • Security: digest (sha256) uniquely identifies image content

Mutability of containers:

  • Containers can be updated without rebuilding images (for legacy apps)
  • Useful for debugging: create a container, inspect its state
  • Can snapshot a container's state and commit it to a new image

Container IDs, Names, and Runtime Tracking

Every container has an ID and optional name.

Container ID

The container ID is the sha256 hash of the container's configuration:

$ docker run myapp:1.0a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6  ← Full container ID (64 chars)

The ID is:

  • Unique: No two containers have the same ID
  • Deterministic: Same config produces same ID
  • Trackable: Runtime uses ID to manage the container

Short ID (first 12 characters) is often used:

$ docker psCONTAINER IDa1b2c3d4

Container Name

You can assign a friendly name:

docker run -d --name my-web-server myapp:1.0docker exec my-web-server ls /appdocker stop my-web-server

Names are:

  • Aliases: my-web-server is easier to remember than a1b2c3d4e5f6g7h8
  • Must be unique: Can't have two containers with the same name
  • Reusable after deletion: Delete a container, you can reuse its name

Tracking

The container runtime (Docker, containerd, CRI-O) maintains metadata:

$ docker inspect a1b2c3d4[  {    "Id": "a1b2c3d4e5f6...",    "Created": "2024-03-22T10:30:45.123Z",    "Path": "/bin/sh",    "Args": ["-c", "python3 app.py"],    "State": {      "Status": "running",      "Pid": 12345,      "ExitCode": 0,      "StartedAt": "2024-03-22T10:30:46.456Z"    },    "Image": "sha256:abc123...",    "Mounts": [      {        "Type": "bind",        "Source": "/home/user/data",        "Destination": "/app/data"      }    ]  }]

The runtime knows:

  • Process ID (Pid: 12345)
  • Image (which image this container came from)
  • Mounts (what volumes are attached)
  • State (running, stopped, created)

Snapshots and Copying Container State

If you want to preserve a container's changes (convert a container back to an image), use docker commit:

# Container has been running, written datadocker run -d --name my-app myapp:1.0docker exec my-app sh -c "echo config > /etc/myapp.conf" # Snapshot the container's state into a new imagedocker commit my-app myapp:1.0.1 # Now myapp:1.0.1 contains all the changesdocker run myapp:1.0.1 cat /etc/myapp.confconfig  ← Change persisted in new image

Behind the scenes: When using docker commit, the container's filesystem (combining all image layers and the container's writable layer) is flattened into a single tarball with all layers merged together, then that tarball becomes a new layer in a new image, which is tagged with the new version (myapp:1.0.1).

This is useful for:

  • Capturing configuration changes
  • Creating a snapshot of a debug container
  • Building images from running containers (not recommended for production)

Next Steps

Understanding the relationship between images and containers is foundational:

Key takeaways:

  • Image is a static blueprint; container is a running instance
  • Multiple containers from one image share read-only layers, have independent writable layers
  • Changes in one container don't affect others (isolation)
  • Container layer persists even when container stops
  • Image is immutable; container is mutable
  • Container IDs are unique, names are aliases
  • docker commit converts a container back to an image (snapshots state)

Practical next steps:

  1. Create a container from an image and modify a file; verify changes are in container layer only
  2. Create 3 containers from one image; verify they're independent
  3. Stop a container, start it again; verify changes persisted
  4. Delete a container; verify its layer is gone
  5. Use docker inspect to understand container metadata
  6. Use docker commit to snapshot a container into a new image

Key Concepts Summary

  • Image: Static artifact (layers + config); immutable and reusable
  • Container: Running instance of an image; mutable with isolated filesystem
  • Relationship: Image is blueprint, container is instance (class vs object)
  • Multiple containers: Can create many containers from one image; they're independent
  • Writable layer: Each container has a unique writable layer on top of shared image layers
  • Copy-on-write: Efficient sharing via kernel filesystem features (OverlayFS)
  • Persistence: Container layer persists when container stops; deleted when container is removed
  • Immutability: Image never changes; containers are mutable
  • Tracking: Container IDs are unique hashes; names are aliases
  • Snapshots: docker commit converts a container's state to a new image