Back to Blogcontainerization · docker · docker-images · docker-services

Services, Images, and Containers

2025-12-22

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.

We use cookies to improve your experience and analyse site traffic. See our Privacy Policy.