Skip to main content

Common Misconfiguration

Mounting sensitive host directories like /etc, /root, or system directories into containers can expose critical system files, credentials, and configurations to potential compromise.

Vulnerable Example

# Vulnerable docker-compose.yml
version: '3.8'
services:
  app:
    image: webapp:latest
    volumes:
      - /etc:/host/etc  # System configuration files exposed
      - /root:/host/root  # Root user home directory
      - ~/.ssh:/app/.ssh  # SSH keys exposed
      - ~/.aws:/app/.aws  # AWS credentials exposed
      - /var/run/docker.sock:/var/run/docker.sock  # Docker socket
      - /:/rootfs  # Entire filesystem mounted

  file-manager:
    image: filebrowser:latest
    volumes:
      - /home:/srv  # All user home directories exposed
      - /etc/passwd:/etc/passwd  # System users file
      - /etc/shadow:/etc/shadow  # Password hashes exposed
    ports:
      - "8080:80"

  logger:
    image: log-collector:latest
    volumes:
      - /var/log:/logs  # All system logs
      - /proc:/host/proc  # Process information
      - /sys:/host/sys  # System information
    user: root  # Running as root makes it worse

Secure Example

# Secure docker-compose.yml
version: '3.8'
services:
  app:
    image: webapp:latest
    volumes:
      # Use named volumes instead of host mounts
      - app_data:/app/data
      - app_config:/app/config:ro
      # If host mount is necessary, be specific and use read-only
      - ./app/public:/app/public:ro
      - type: tmpfs
        target: /app/tmp
        tmpfs:
          size: 100m
          mode: 1777
    configs:
      - source: app_settings
        target: /app/settings.yml
        mode: 0440
    secrets:
      - source: app_credentials
        target: /app/.credentials
        uid: '1000'
        gid: '1000'
        mode: 0400
    user: "1000:1000"
    read_only: true

  file-manager:
    image: filebrowser:latest
    volumes:
      # Mount only specific, non-sensitive directories
      - ./shared/documents:/srv/documents:ro
      - ./shared/public:/srv/public
      - filebrowser_db:/database
    environment:
      - FB_DATABASE=/database/filebrowser.db
      - FB_ROOT=/srv
      - FB_NOAUTH=false
    ports:
      - "127.0.0.1:8080:80"  # Bind only to localhost
    user: "1001:1001"
    security_opt:
      - no-new-privileges:true

  logger:
    image: log-collector:latest
    volumes:
      # Mount only specific log directories with restrictions
      - ./logs/app:/logs/app:ro
      - type: bind
        source: /var/log/myapp
        target: /logs/system
        read_only: true
        bind:
          propagation: slave
    cap_drop:
      - ALL
    cap_add:
      - DAC_READ_SEARCH  # Only for reading files
    user: "1002:1002"
    security_opt:
      - no-new-privileges:true
      - seccomp:default

volumes:
  app_data:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: ./volumes/app_data
  app_config:
    driver: local
  filebrowser_db:
    driver: local

configs:
  app_settings:
    file: ./configs/app_settings.yml

secrets:
  app_credentials:
    file: ./secrets/credentials.json

Volume Security Best Practices

# Example: Secure data processing pipeline
version: '3.8'
services:
  data-processor:
    image: processor:latest
    volumes:
      # Input directory - read-only
      - type: bind
        source: ./input
        target: /data/input
        read_only: true
        bind:
          propagation: rprivate
      
      # Output directory - specific path with limited access
      - type: bind
        source: ./output
        target: /data/output
        bind:
          propagation: rprivate
      
      # Temporary processing - use tmpfs
      - type: tmpfs
        target: /tmp
        tmpfs:
          size: 500m
          mode: 1770
      
      # Named volume for persistent data
      - processed_data:/data/processed
    
    # Security context
    user: "2000:2000"
    group_add:
      - "2001"  # Additional group for shared access
    security_opt:
      - no-new-privileges:true
      - label:type:container_file_t
    cap_drop:
      - ALL
    read_only: true

  # Example: Secure backup service
  backup:
    image: restic:latest
    volumes:
      # Mount only backup sources as read-only
      - type: bind
        source: /data/important
        target: /source/important
        read_only: true
      
      # Backup destination with specific permissions
      - type: volume
        source: backup_storage
        target: /backup
        volume:
          nocopy: true
    
    # Use secrets for backup encryption
    secrets:
      - restic_password
      - restic_repository
    
    environment:
      - RESTIC_PASSWORD_FILE=/run/secrets/restic_password
      - RESTIC_REPOSITORY_FILE=/run/secrets/restic_repository
    
    user: "2002:2002"
    cap_drop:
      - ALL
    cap_add:
      - DAC_READ_SEARCH
      - CHOWN

volumes:
  processed_data:
    driver: local
    labels:
      com.example.description: "Processed data storage"
      com.example.department: "Data Processing"
  
  backup_storage:
    driver: local
    driver_opts:
      type: nfs
      o: addr=10.0.0.10,nolock,soft,rw
      device: ":/exports/backups"

secrets:
  restic_password:
    external: true
  restic_repository:
    external: true

Directory Permission Script

#!/bin/bash
# setup-volumes.sh - Prepare volume directories with proper permissions

# Create volume directories
mkdir -p ./volumes/{app_data,app_config,processed_data}
mkdir -p ./shared/{documents,public}
mkdir -p ./logs/app
mkdir -p ./input ./output

# Set restrictive permissions
chmod 750 ./volumes/*
chmod 755 ./shared/public
chmod 750 ./shared/documents
chmod 750 ./logs/app
chmod 750 ./input
chmod 770 ./output

# Set ownership (adjust UIDs to match container users)
chown 1000:1000 ./volumes/app_data
chown 1000:1000 ./volumes/app_config
chown 1001:1001 ./shared/*
chown 1002:1002 ./logs/app
chown 2000:2000 ./input ./output

# Set SELinux context if applicable
if command -v chcon &> /dev/null; then
  chcon -Rt svirt_sandbox_file_t ./volumes
  chcon -Rt svirt_sandbox_file_t ./shared
  chcon -Rt svirt_sandbox_file_t ./logs
fi