Understanding the relationship between services, images, and containers is fundamental to working with multi-container applications. These three concepts form the core building blocks of containerized infrastructure, each serving a distinct purpose while working together to create functioning applications.
The Three-Layer Architecture
Services, images, and containers exist in a hierarchical relationship. Images serve as templates, containers are running instances created from those templates, and services are abstract definitions that describe how containers should be created and managed. This layered architecture provides both flexibility and consistency in application deployment.
The separation between these layers allows you to define infrastructure once at the service level, use images as reusable components, and run multiple containers from the same image without conflict. Understanding how these layers interact is essential for effectively managing containerized applications.
What Are Images
Images are read-only templates that contain everything needed to run an application. They include the application code, runtime, system tools, libraries, and settings. Images are built in layers, with each layer representing a set of filesystem changes. This layered structure makes images efficient to store and transfer.
When you reference an image, you're specifying which template should be used to create containers. Images can be pulled from registries like Docker Hub, or they can be built locally from source code. The image acts as the blueprint from which identical containers can be spawned.
Image Naming and Tags
Images follow a specific naming convention that identifies them uniquely. A full image reference includes a registry hostname, repository name, and tag. The tag typically indicates a version or variant of the image.
services:
web:
image: nginx:1.24
# Registry: docker.io (default)
# Repository: nginx
# Tag: 1.24
database:
image: postgres:15-alpine
# Repository: postgres
# Tag: 15-alpine (version 15, alpine variant)
custom:
image: myregistry.com/myapp:v2.1.0
# Registry: myregistry.com
# Repository: myapp
# Tag: v2.1.0
When no tag is specified, the default latest tag is used. However, using explicit version tags is recommended for production environments to ensure consistency and predictability.
Image Variants and Tags
Many official images provide multiple variants optimized for different use cases. Common variant suffixes include alpine for minimal size, slim for reduced size with more features, and specific version numbers for stability.
services:
# Full-featured image
app1:
image: python:3.11
# Alpine variant - minimal size
app2:
image: python:3.11-alpine
# Slim variant - smaller than full, larger than alpine
app3:
image: python:3.11-slim
# Specific minor version
app4:
image: python:3.11.7
# Specific build variant
app5:
image: node:18-bullseye
Choosing the appropriate variant depends on your application's requirements, including size constraints, available system tools, and compatibility needs.
Image Digests
Images can be referenced by their content hash or digest, which provides an immutable reference to a specific image version. Unlike tags, which can be reassigned to different images, digests always point to the same image content.
services:
web:
image: nginx@sha256:4c0fdaa8b6341bfdeca5f18f7837462c80cff90527ee35ef185571e1c327beac
# This digest will always refer to the exact same image
api:
image: myapp:v1.0@sha256:abc123...
# Can combine tag and digest for clarity and immutability
Using digests ensures that deployments are completely reproducible, as the exact same image will be used regardless of any tag updates.
What Are Containers
Containers are runtime instances created from images. When you start a container, the container runtime creates a writable layer on top of the read-only image layers. This writable layer captures all changes made during the container's lifetime, including file modifications, created files, and process state.
Each container runs in isolation with its own filesystem, network interface, and process space. Multiple containers created from the same image are independent of each other—changes in one container don't affect others or the underlying image.
Container Lifecycle
Containers have distinct lifecycle states: created, running, paused, stopped, and removed. A container can transition between these states based on commands and events. Understanding this lifecycle helps in managing container behavior and troubleshooting issues.
services:
web:
image: nginx
# When started, creates a container in 'running' state
# Container can be stopped, paused, or removed
# Each state transition affects container behavior
worker:
image: python:3.11
# Multiple containers can be created from same service definition
# Each container has independent lifecycle
Containers are designed to be ephemeral—they can be stopped and removed without affecting the image or other containers. This ephemeral nature encourages treating containers as disposable units that can be easily recreated.
Container Naming
Containers receive names that identify them uniquely on the host system. By default, containers get automatically generated names, but you can specify custom names for easier identification and management.
services:
web:
image: nginx
container_name: my-web-server
# Creates a container named 'my-web-server'
# Must be unique across all containers on the host
api:
image: node:18
# Without container_name, gets auto-generated name
# Format: projectname_servicename_instance
Custom container names provide clarity but limit flexibility—you can only run one container with a given name at a time. Auto-generated names allow multiple instances of the same service.
What Are Services
Services are abstract definitions that describe how containers should be created and configured. A service definition specifies which image to use, how many container instances to run, what configuration to apply, and how the containers should behave. Services provide a declarative way to define container infrastructure.
The service is the configuration layer—it doesn't run by itself but describes what should run. When you work with services, you're defining the desired state of your infrastructure, and the container runtime works to achieve and maintain that state.
Service Definitions
Service definitions contain all the information needed to create and configure containers. This includes the image reference, resource constraints, networking configuration, and runtime behavior specifications.
services:
# Service name: web
web:
image: nginx:alpine
# Defines what image to use
ports:
- "8080:80"
# Defines port mapping
restart: unless-stopped
# Defines restart behavior
deploy:
replicas: 3
# Defines number of container instances
# Service name: database
database:
image: postgres:15
# Different service, different configuration
hostname: db-server
# Sets container hostname
Each service definition is independent, allowing you to configure different containers with different requirements within the same application stack.
Service Names and Container Identity
Service names serve as identifiers within your application configuration. These names become DNS hostnames that other services can use to communicate. The service name creates an abstraction layer between the logical service and the physical containers.
services:
frontend:
image: webapp:latest
# Service name: frontend
# Other services can reference this as 'frontend'
backend:
image: api:latest
# Service name: backend
# Can be reached by other containers as 'backend'
cache:
image: redis:7
# Service name: cache
# Internal DNS resolves 'cache' to this service's containers
Service names provide stable references even when underlying containers are recreated or scaled. Applications can use service names without knowing about specific container instances.
The Relationship Between Services, Images, and Containers
Services define the blueprint for creating containers. Images provide the template from which containers are instantiated. Containers are the running instances that execute your applications. This three-layer model separates concerns: services describe what to run, images define what's available to run, and containers are what actually runs.
services:
# SERVICE LAYER: Definition
application:
# IMAGE LAYER: Template
image: myapp:1.0
# Service configuration applies to all containers
# created from this definition
# Multiple services can use the same image
worker:
image: myapp:1.0
# Same image, different service definition
# Creates different containers with different configuration
When you start a service, the system pulls or verifies the specified image, then creates containers according to the service definition. Each container is an independent instance but shares the same underlying image layers.
Building Images Within Service Definitions
Instead of referencing pre-built images, services can specify build instructions. This approach creates images on-demand from source code, making them available for container creation.
services:
web:
build:
context: ./web-app
dockerfile: Dockerfile
# Builds an image from source code
# Then creates containers from that built image
api:
build: ./api
# Shorthand: uses Dockerfile in ./api directory
# Image is built before containers are created
worker:
build:
context: ./worker
dockerfile: Dockerfile.production
# Can specify alternate Dockerfile names
The build process creates an image locally, which then serves as the template for containers. This pattern integrates image creation into the service definition workflow.
Build Arguments
Build-time arguments allow passing variables during the image build process. These arguments customize the image creation without modifying the build instructions themselves.
services:
app:
build:
context: .
args:
- NODE_VERSION=18
- BUILD_ENV=production
# Arguments passed to image build process
# Affect how the image is constructed
backend:
build:
context: ./backend
args:
PYTHON_VERSION: "3.11"
INSTALL_DEV: "false"
# Can use mapping format for arguments
# Values available during image build
Build arguments influence the resulting image, which then serves as the template for creating containers. Different argument values produce different images.
Build Target Selection
Multi-stage builds allow creating different image variants from the same source. The target parameter specifies which build stage to use as the final image.
services:
development:
build:
context: .
target: dev
# Uses 'dev' stage from multi-stage build
# Creates image optimized for development
production:
build:
context: .
target: prod
# Uses 'prod' stage from same build file
# Creates different image optimized for production
Each target produces a distinct image, and containers created from these images have different contents and configurations despite originating from the same source.
Image Pulling Policies
Services can specify when images should be pulled from registries. The pull policy controls whether to use local images or fetch updated versions from remote sources.
services:
web:
image: nginx:latest
pull_policy: always
# Always pulls image from registry
# Ensures latest version is used
cache:
image: redis:7
pull_policy: if_not_present
# Only pulls if image doesn't exist locally
# Uses cached image when available
stable:
image: postgres:15.2
pull_policy: never
# Never pulls from registry
# Must exist locally or fails
Pull policies affect which image version becomes the template for containers. Different policies balance between freshness and speed.
Container Resource Allocation
Services define resource constraints that apply to created containers. These constraints control how much CPU, memory, and other resources each container can consume.
services:
web:
image: nginx
deploy:
resources:
limits:
cpus: '0.50'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
# Containers from this service have resource limits
# Each instance respects these constraints
database:
image: postgres
deploy:
resources:
limits:
memory: 2G
# Different service, different resource allocation
Resource definitions at the service level ensure all containers created from that service have consistent resource constraints.
Container Runtime Configuration
Services specify runtime behavior for containers, including how they should start, stop, and restart. These configurations ensure containers behave appropriately in different situations.
services:
web:
image: nginx
restart: unless-stopped
# Containers automatically restart on failure
# Except when manually stopped
worker:
image: processor
restart: on-failure
# Containers restart only after non-zero exit
oneshot:
image: migration
restart: "no"
# Containers don't restart after exit
Runtime configuration at the service level provides consistent behavior across all containers created from that service definition.
Container Command and Entrypoint
Services can override the default command and entrypoint defined in images. This customization allows running the same image in different ways for different services.
services:
web:
image: python:3.11
command: python app.py
# Overrides image's default command
# All containers run this command instead
worker:
image: python:3.11
command: celery worker
# Same image, different command
# Creates containers with different behavior
shell:
image: python:3.11
entrypoint: /bin/bash
command: -c "while true; do sleep 1000; done"
# Overrides both entrypoint and command
Command and entrypoint overrides at the service level allow reusing images for different purposes, with each service creating containers configured for specific tasks.
Container User and Permissions
Services specify which user account containers should run as. This configuration affects file permissions and security within the container.
services:
web:
image: nginx
user: "1000:1000"
# Containers run as user ID 1000, group ID 1000
# Affects file access and process ownership
rootless:
image: myapp
user: appuser
# Containers run as named user from image
# Reduces security risk of root access
privileged:
image: system-tool
privileged: true
# Containers run with extended privileges
# Caution: security implications
User configuration at the service level ensures consistent permission models across all containers created from that service.
Container Hostname and Domain
Services can specify hostnames for containers, which affects how containers identify themselves and how they appear in logs and process listings.
services:
web:
image: nginx
hostname: web-server
# Each container gets this hostname
# Appears in container's /etc/hostname
api:
image: node
hostname: api-backend
domainname: internal.local
# Sets both hostname and domain
# Full name: api-backend.internal.local
Hostname configuration helps with identification and can be important for applications that check their hostname or for monitoring systems that track container identity.
Multiple Containers from One Service
A single service definition can create multiple container instances. Each instance is independent but shares the same configuration defined in the service.
services:
worker:
image: task-processor
deploy:
replicas: 5
# Creates 5 independent containers
# All from same image and configuration
loadbalanced:
image: webapp
deploy:
replicas: 3
# Multiple containers provide redundancy
# Each handles requests independently
Multiple containers from one service provide scalability and redundancy. Each container runs the same code but processes independently, allowing parallel work.
Container Working Directory
Services specify the working directory for containers, determining where commands execute and where relative paths are resolved.
services:
app:
image: node:18
working_dir: /app
command: npm start
# Command executes in /app directory
# Relative paths resolved from /app
processor:
image: python:3.11
working_dir: /opt/processor
# Sets different working directory
# Affects where processes run
Working directory configuration ensures commands and applications start in the correct location within the container's filesystem.
Container Labels and Metadata
Services can attach labels to containers for organization, filtering, and metadata storage. Labels are key-value pairs that don't affect container runtime but provide information for management tools.
services:
web:
image: nginx
labels:
- "com.example.description=Frontend web server"
- "com.example.department=engineering"
- "com.example.version=1.0"
# Containers get these labels
# Used for organization and filtering
api:
image: api-server
labels:
environment: "production"
tier: "backend"
monitoring: "enabled"
# Different label format, same purpose
Labels help organize and identify containers without changing their behavior. Management and monitoring tools can use labels to group and filter containers.
Container Health Status
Images can define health check mechanisms, and services can override or configure these checks. Health checks determine whether containers are functioning correctly.
services:
web:
image: nginx
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# Containers periodically checked for health
# Unhealthy containers can trigger restarts
database:
image: postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
# Different check for different service
Health checks at the service level ensure all containers from that service are monitored consistently, helping maintain application reliability.
Container Stop Behavior
Services configure how containers should stop, including grace periods and stop signals. This configuration ensures clean shutdowns.
services:
web:
image: nginx
stop_grace_period: 30s
# Containers get 30 seconds to stop gracefully
# After timeout, forced termination
database:
image: postgres
stop_grace_period: 60s
stop_signal: SIGINT
# Longer grace period for database
# Custom stop signal
Stop configuration ensures containers shut down properly, allowing them to close connections, save state, and clean up resources before termination.
Image Platform Specification
Services can specify which platform architecture containers should use. This becomes relevant when working with multi-architecture images or specific CPU architectures.
services:
web:
image: nginx
platform: linux/amd64
# Forces amd64 platform even on ARM host
# Uses emulation if necessary
arm-service:
image: myapp
platform: linux/arm64
# Specifies ARM64 architecture
multi:
image: python:3.11
platform: linux/arm/v7
# Specific ARM variant
Platform specification ensures containers run with the correct architecture, which is important for compatibility and performance.
Container Security Options
Services can configure security settings that affect how containers interact with the host system and what capabilities they have.
services:
restricted:
image: webapp
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
# Drops all capabilities, adds only necessary ones
# Minimizes container privileges
isolated:
image: processor
security_opt:
- no-new-privileges:true
# Prevents privilege escalation
# Additional security hardening
readonly:
image: static-server
read_only: true
# Container filesystem is read-only
# Prevents runtime modifications
Security configurations at the service level ensure all containers maintain consistent security postures, reducing vulnerability surface.
Container Lifecycle Hooks
Services can define actions that occur at specific points in container lifecycle, allowing custom initialization and cleanup procedures.
services:
app:
image: myapp
depends_on:
database:
condition: service_healthy
# Container waits for dependency health
# Affects creation timing
worker:
image: processor
depends_on:
- cache
- queue
# Container creation waits for dependencies
# Ensures required services exist
Lifecycle dependencies ensure containers start in the correct order, with required services available before dependent services begin.
Container Temporary Filesystem
Services can configure temporary filesystems within containers, providing writable space in otherwise read-only containers.
services:
app:
image: webapp
tmpfs:
- /tmp
- /var/run
# Creates in-memory filesystems
# Fast, temporary storage
cache:
image: processor
tmpfs:
/cache:
size: 100M
# Temporary filesystem with size limit
# Cleared on container restart
Temporary filesystems provide writable space without persisting data, useful for caches, temporary files, and runtime state that doesn't need to survive container restarts.
Container Process Limits
Services can set limits on the number of processes containers can create. This prevents resource exhaustion from process bombs or runaway applications.
services:
web:
image: nginx
pids_limit: 100
# Container can create maximum 100 processes
# Protects against fork bombs
unlimited:
image: batch-processor
pids_limit: -1
# No process limit
# Use cautiously
Process limits ensure containers can't consume excessive system resources through unlimited process creation.
Service Dependencies and Startup Order
Services can declare dependencies on other services, creating startup ordering. Dependent containers wait for their dependencies before starting.
services:
web:
image: nginx
depends_on:
- api
- cache
# Web containers wait for api and cache
# Ensures dependencies exist first
api:
image: api-server
depends_on:
database:
condition: service_started
# API waits for database to start
# Specific dependency conditions
database:
image: postgres
# No dependencies, starts first
Dependency declarations control startup order, ensuring containers start in sequences that respect application architecture requirements.
Container Image Update Behavior
Services define whether containers should be recreated when images update. This configuration affects how application updates are deployed.
services:
stable:
image: myapp:1.0
# Fixed version, predictable behavior
# Containers not recreated unless explicitly updated
rolling:
image: myapp:latest
pull_policy: always
# Always pulls latest image
# Containers recreated with new image versions
pinned:
image: myapp@sha256:abc123...
# Digest ensures exact image version
# Immutable reference
Update behavior at the service level determines how frequently containers are recreated and whether they automatically pick up new image versions.
Advanced Container Configuration
Services support numerous advanced configuration options that fine-tune container behavior for specific use cases.
services:
tuned:
image: optimized-app
# CPU configuration
cpu_count: 2
cpu_percent: 50
cpus: 1.5
cpu_shares: 512
# Memory configuration
mem_limit: 1G
memswap_limit: 2G
mem_reservation: 512M
# IO configuration
blkio_config:
weight: 300
# Device configuration
device_cgroup_rules:
- 'c 1:3 mr'
# System configuration
shm_size: 128M
sysctls:
net.core.somaxconn: 1024
Advanced configurations allow precise control over container resource usage and system interactions, optimizing performance for specific workload requirements.
Container State Persistence
While containers are ephemeral, services can configure aspects that affect how state is managed during container lifecycle.
services:
stateful:
image: database
# Container state lost on removal
# Data persistence requires additional configuration
cache:
image: redis
# In-memory state doesn't persist
# Container restart clears data
processor:
image: worker
# Process state doesn't survive container restart
# Application must handle state externally
Understanding container state behavior is crucial for designing resilient applications. Containers themselves don't persist state—external mechanisms are needed for data persistence.
Service-Level vs Container-Level Configuration
Configuration specified at the service level applies to all containers created from that service. This distinction is important when working with multiple container instances.
services:
workers:
image: processor
deploy:
replicas: 5
# All 5 containers share:
# - Same image
# - Same resource limits
# - Same restart policy
# - Same security settings
# Each container has:
# - Unique container ID
# - Independent lifecycle
# - Separate process space
# - Individual network interface
Service configuration provides templates, while containers are the actual running instances. Changes to service configuration don't affect existing containers until they're recreated.
Practical Service Configuration Patterns
Real-world service definitions combine multiple configuration aspects to create well-behaved containers.
services:
production-web:
# Image specification
image: webapp:2.1.0
# Container identity
container_name: web-prod-01
hostname: web-server
# Runtime behavior
restart: unless-stopped
stop_grace_period: 30s
# Resource management
deploy:
resources:
limits:
cpus: '1.0'
memory: 1G
reservations:
cpus: '0.5'
memory: 512M
# Security
user: "1000:1000"
read_only: true
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
# Health monitoring
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
# Organization
labels:
com.example.service: "web"
com.example.environment: "production"
Comprehensive service configurations ensure containers are properly constrained, secured, monitored, and identified for production use.