Skip to content

Containers (Docker)

Introduction

Containers are lightweight, portable, and self-sufficient packages that include everything needed to run an application: code, runtime, system tools, libraries, and settings. Docker is the most popular containerization platform that enables developers to package applications into containers.

Docker Architecture

Container Lifecycle

Multi-Service Architecture

Docker Fundamentals

Docker Architecture

bash
# Docker daemon (dockerd)
sudo systemctl start docker
sudo systemctl enable docker

# Docker client commands
docker version
docker info
docker --help

# Container lifecycle
docker create --name my-container nginx
docker start my-container
docker stop my-container
docker restart my-container
docker pause my-container
docker unpause my-container
docker rm my-container

# Image management
docker images
docker pull nginx:latest
docker push myregistry/myapp:v1.0.0
docker rmi nginx:latest
docker image prune

Dockerfile Best Practices

Multi-stage Dockerfile Example

dockerfile
# Dockerfile
# Stage 1: Build stage
FROM maven:3.8.4-openjdk-11-slim AS builder

WORKDIR /app

# Copy dependency files first for better caching
COPY pom.xml .
COPY src/main/resources/application.yml src/main/resources/

# Download dependencies
RUN mvn dependency:go-offline -B

# Copy source code and build
COPY src ./src
RUN mvn clean package -DskipTests

# Stage 2: Runtime stage
FROM openjdk:11-jre-slim

# Create non-root user for security
RUN groupadd -r appuser && useradd -r -g appuser appuser

# Install necessary packages and clean up
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
        curl \
        dumb-init && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

# Set working directory
WORKDIR /app

# Create necessary directories
RUN mkdir -p /app/logs /app/temp && \
    chown -R appuser:appuser /app

# Copy JAR from builder stage
COPY --from=builder /app/target/my-web-app-1.0.0.jar app.jar

# Copy configuration files
COPY --chown=appuser:appuser docker/application-docker.yml application.yml
COPY --chown=appuser:appuser docker/entrypoint.sh entrypoint.sh

# Make entrypoint executable
RUN chmod +x entrypoint.sh

# Switch to non-root user
USER appuser

# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
    CMD curl -f http://localhost:8080/actuator/health || exit 1

# Expose port
EXPOSE 8080

# Use dumb-init to handle signals properly
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["./entrypoint.sh"]

Optimized Spring Boot Dockerfile

dockerfile
# Dockerfile.layered
FROM openjdk:11-jre-slim

# Create non-root user
RUN groupadd -r spring && useradd -r -g spring spring

# Install required packages
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
        curl \
        dumb-init && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

WORKDIR /app

# Copy layered JAR contents (Spring Boot 2.3+)
COPY --chown=spring:spring target/extracted/dependencies/ ./
COPY --chown=spring:spring target/extracted/spring-boot-loader/ ./
COPY --chown=spring:spring target/extracted/snapshot-dependencies/ ./
COPY --chown=spring:spring target/extracted/application/ ./

# Copy configuration
COPY --chown=spring:spring docker/application-docker.yml application.yml

# Switch to non-root user
USER spring

# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
    CMD curl -f http://localhost:8080/actuator/health || exit 1

EXPOSE 8080

ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["java", "-Dspring.profiles.active=docker", "org.springframework.boot.loader.JarLauncher"]

Entrypoint Script

bash
#!/bin/bash
# docker/entrypoint.sh

set -e

# Default JVM options
DEFAULT_JVM_OPTS="-Xmx512m -Xms256m -XX:+UseG1GC -XX:+UseContainerSupport"

# Use custom JVM options if provided
JVM_OPTS=${JVM_OPTS:-$DEFAULT_JVM_OPTS}

# Default Spring profiles
SPRING_PROFILES=${SPRING_PROFILES_ACTIVE:-docker}

# Wait for database to be ready
if [ -n "$DATABASE_HOST" ]; then
    echo "Waiting for database at $DATABASE_HOST:${DATABASE_PORT:-5432}..."
    while ! nc -z "$DATABASE_HOST" "${DATABASE_PORT:-5432}"; do
        sleep 1
    done
    echo "Database is ready!"
fi

# Start the application
exec java $JVM_OPTS \
    -Djava.security.egd=file:/dev/./urandom \
    -Dspring.profiles.active="$SPRING_PROFILES" \
    -jar app.jar "$@"

Docker Compose Configuration

Complete Multi-Service Setup

yaml
# docker-compose.yml
version: '3.8'

services:
  web-app:
    build:
      context: .
      dockerfile: Dockerfile
    image: myregistry/web-app:1.0.0
    container_name: web-app
    ports:
      - "8080:8080"
    environment:
      - SPRING_PROFILES_ACTIVE=docker
      - DATABASE_URL=jdbc:postgresql://postgres:5432/myapp
      - DATABASE_USERNAME=postgres
      - DATABASE_PASSWORD_FILE=/run/secrets/db_password
      - REDIS_URL=redis://redis:6379
      - JWT_SECRET_FILE=/run/secrets/jwt_secret
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    volumes:
      - app-logs:/app/logs
      - app-temp:/app/temp
    secrets:
      - db_password
      - jwt_secret
    networks:
      - app-network
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

  postgres:
    image: postgres:13
    container_name: postgres-db
    environment:
      - POSTGRES_DB=myapp
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD_FILE=/run/secrets/db_password
      - PGDATA=/var/lib/postgresql/data/pgdata
    volumes:
      - postgres-data:/var/lib/postgresql/data
      - ./docker/init-db.sql:/docker-entrypoint-initdb.d/init-db.sql:ro
    secrets:
      - db_password
    networks:
      - app-network
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 30s
      timeout: 10s
      retries: 5
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

  redis:
    image: redis:6-alpine
    container_name: redis-cache
    command: redis-server --appendonly yes --requirepass "$(cat /run/secrets/redis_password)"
    volumes:
      - redis-data:/data
    secrets:
      - redis_password
    networks:
      - app-network
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 30s
      timeout: 10s
      retries: 3
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

  nginx:
    image: nginx:alpine
    container_name: nginx-proxy
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./docker/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./docker/ssl:/etc/nginx/ssl:ro
      - nginx-logs:/var/log/nginx
    depends_on:
      - web-app
    networks:
      - app-network
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "wget", "-q", "--spider", "http://localhost/health"]
      interval: 30s
      timeout: 10s
      retries: 3

volumes:
  postgres-data:
    driver: local
  redis-data:
    driver: local
  app-logs:
    driver: local
  app-temp:
    driver: local
  nginx-logs:
    driver: local

secrets:
  db_password:
    file: ./secrets/db_password.txt
  jwt_secret:
    file: ./secrets/jwt_secret.txt
  redis_password:
    file: ./secrets/redis_password.txt

networks:
  app-network:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/16

Development Override

yaml
# docker-compose.override.yml
version: '3.8'

services:
  web-app:
    build:
      target: development
    environment:
      - SPRING_PROFILES_ACTIVE=development
      - DEBUG=true
    volumes:
      - .:/app
      - ~/.m2:/root/.m2
    ports:
      - "5005:5005" # Debug port
    command: ["./mvnw", "spring-boot:run", "-Dspring-boot.run.jvmArguments=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"]

  postgres:
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_DB=myapp_dev

  redis:
    ports:
      - "6379:6379"

Production Configuration

yaml
# docker-compose.prod.yml
version: '3.8'

services:
  web-app:
    deploy:
      replicas: 3
      resources:
        limits:
          cpus: '1.0'
          memory: 1G
        reservations:
          cpus: '0.5'
          memory: 512M
      restart_policy:
        condition: any
        delay: 5s
        max_attempts: 3
        window: 120s
    environment:
      - SPRING_PROFILES_ACTIVE=production
      - JVM_OPTS=-Xmx768m -Xms512m -XX:+UseG1GC
    logging:
      driver: "fluentd"
      options:
        fluentd-address: "fluentd:24224"
        tag: "docker.web-app"

  postgres:
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 2G
        reservations:
          cpus: '1.0'
          memory: 1G
    volumes:
      - /data/postgres:/var/lib/postgresql/data

  redis:
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 256M

Container Security Implementation

Security Configuration Service

java
// ContainerSecurityService.java
package com.mycompany.security;

import org.springframework.boot.actuate.security.AuthenticationAuditListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.http.HttpServletRequest;
import java.util.List;

@Configuration
@EnableWebSecurity
public class ContainerSecurityConfig {
    
    private final JwtAuthenticationFilter jwtAuthenticationFilter;
    private final CustomAuthenticationEntryPoint authenticationEntryPoint;
    
    public ContainerSecurityConfig(
            JwtAuthenticationFilter jwtAuthenticationFilter,
            CustomAuthenticationEntryPoint authenticationEntryPoint) {
        this.jwtAuthenticationFilter = jwtAuthenticationFilter;
        this.authenticationEntryPoint = authenticationEntryPoint;
    }
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/actuator/health", "/actuator/info").permitAll()
                .requestMatchers("/actuator/**").hasRole("ADMIN")
                .requestMatchers("/api/auth/**").permitAll()
                .requestMatchers("/api/public/**").permitAll()
                .anyRequest().authenticated()
            )
            .exceptionHandling()
                .authenticationEntryPoint(authenticationEntryPoint)
            .and()
            .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
            .headers(headers -> headers
                .frameOptions().deny()
                .contentTypeOptions().and()
                .httpStrictTransportSecurity(hstsConfig -> hstsConfig
                    .maxAgeInSeconds(31536000)
                    .includeSubdomains(true)
                )
                .and()
            );
        
        return http.build();
    }
    
    @Bean
    public AuthenticationAuditListener authenticationAuditListener() {
        return new AuthenticationAuditListener();
    }
}

// Container environment security
@Component
public class ContainerEnvironmentValidator {
    
    private static final Logger logger = LoggerFactory.getLogger(ContainerEnvironmentValidator.class);
    
    @PostConstruct
    public void validateSecurityConfiguration() {
        validateFilePermissions();
        validateNetworkConfiguration();
        validateEnvironmentVariables();
        validateSecrets();
    }
    
    private void validateFilePermissions() {
        try {
            Path appDir = Paths.get("/app");
            Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(appDir);
            
            if (permissions.contains(PosixFilePermission.OTHERS_WRITE)) {
                logger.warn("Application directory has world-write permissions");
            }
            
            // Check for sensitive files
            Files.walk(appDir)
                .filter(path -> path.toString().contains("secret") || 
                               path.toString().contains("password"))
                .forEach(this::checkFilePermissions);
                
        } catch (IOException e) {
            logger.error("Error validating file permissions", e);
        }
    }
    
    private void checkFilePermissions(Path file) {
        try {
            Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(file);
            if (permissions.contains(PosixFilePermission.OTHERS_READ) ||
                permissions.contains(PosixFilePermission.OTHERS_WRITE)) {
                logger.warn("Sensitive file {} has permissive permissions: {}", 
                           file, permissions);
            }
        } catch (IOException e) {
            logger.error("Error checking permissions for file: " + file, e);
        }
    }
    
    private void validateNetworkConfiguration() {
        // Validate network interfaces and exposed ports
        String exposedPorts = System.getenv("EXPOSED_PORTS");
        if (exposedPorts != null) {
            logger.info("Container exposed ports: {}", exposedPorts);
        }
    }
    
    private void validateEnvironmentVariables() {
        Map<String, String> env = System.getenv();
        
        // Check for sensitive data in environment variables
        env.entrySet().stream()
            .filter(entry -> entry.getKey().toLowerCase().contains("password") ||
                           entry.getKey().toLowerCase().contains("secret") ||
                           entry.getKey().toLowerCase().contains("key"))
            .forEach(entry -> {
                if (!entry.getKey().endsWith("_FILE")) {
                    logger.warn("Potential sensitive data in environment variable: {}", 
                               entry.getKey());
                }
            });
    }
    
    private void validateSecrets() {
        // Validate that secrets are properly mounted
        Path secretsDir = Paths.get("/run/secrets");
        if (Files.exists(secretsDir)) {
            try {
                Files.list(secretsDir)
                    .forEach(secret -> logger.info("Secret file available: {}", 
                                                  secret.getFileName()));
            } catch (IOException e) {
                logger.error("Error reading secrets directory", e);
            }
        }
    }
}

Docker Registry Implementation

Private Registry Service

java
// DockerRegistryService.java
package com.mycompany.registry;

import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.http.*;
import org.springframework.util.Base64Utils;

import java.util.*;

@Service
public class DockerRegistryService {
    
    private final RestTemplate restTemplate;
    private final String registryUrl;
    private final String username;
    private final String password;
    
    public DockerRegistryService(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
        this.registryUrl = System.getenv("REGISTRY_URL");
        this.username = System.getenv("REGISTRY_USERNAME");
        this.password = System.getenv("REGISTRY_PASSWORD");
    }
    
    public void pushImage(String imageName, String tag, byte[] imageData) {
        try {
            // Authenticate with registry
            String authToken = authenticate();
            
            // Upload image layers
            String uploadUrl = initiateUpload(imageName, authToken);
            uploadImageLayers(uploadUrl, imageData, authToken);
            
            // Create and upload manifest
            String manifest = createManifest(imageName, tag);
            uploadManifest(imageName, tag, manifest, authToken);
            
        } catch (Exception e) {
            throw new RuntimeException("Failed to push image to registry", e);
        }
    }
    
    public ImageMetadata pullImageMetadata(String imageName, String tag) {
        try {
            String authToken = authenticate();
            String manifestUrl = String.format("%s/v2/%s/manifests/%s", 
                                              registryUrl, imageName, tag);
            
            HttpHeaders headers = createAuthHeaders(authToken);
            headers.add("Accept", "application/vnd.docker.distribution.manifest.v2+json");
            
            HttpEntity<String> entity = new HttpEntity<>(headers);
            ResponseEntity<String> response = restTemplate.exchange(
                manifestUrl, HttpMethod.GET, entity, String.class);
            
            return parseManifest(response.getBody());
            
        } catch (Exception e) {
            throw new RuntimeException("Failed to pull image metadata", e);
        }
    }
    
    public List<String> listRepositories() {
        try {
            String authToken = authenticate();
            String catalogUrl = registryUrl + "/v2/_catalog";
            
            HttpHeaders headers = createAuthHeaders(authToken);
            HttpEntity<String> entity = new HttpEntity<>(headers);
            
            ResponseEntity<RepositoryCatalog> response = restTemplate.exchange(
                catalogUrl, HttpMethod.GET, entity, RepositoryCatalog.class);
            
            return response.getBody().getRepositories();
            
        } catch (Exception e) {
            throw new RuntimeException("Failed to list repositories", e);
        }
    }
    
    public List<String> listTags(String repositoryName) {
        try {
            String authToken = authenticate();
            String tagsUrl = String.format("%s/v2/%s/tags/list", registryUrl, repositoryName);
            
            HttpHeaders headers = createAuthHeaders(authToken);
            HttpEntity<String> entity = new HttpEntity<>(headers);
            
            ResponseEntity<TagsList> response = restTemplate.exchange(
                tagsUrl, HttpMethod.GET, entity, TagsList.class);
            
            return response.getBody().getTags();
            
        } catch (Exception e) {
            throw new RuntimeException("Failed to list tags for repository: " + repositoryName, e);
        }
    }
    
    public void deleteImage(String imageName, String tag) {
        try {
            String authToken = authenticate();
            
            // Get manifest digest
            String digest = getManifestDigest(imageName, tag, authToken);
            
            // Delete manifest
            String deleteUrl = String.format("%s/v2/%s/manifests/%s", 
                                            registryUrl, imageName, digest);
            
            HttpHeaders headers = createAuthHeaders(authToken);
            HttpEntity<String> entity = new HttpEntity<>(headers);
            
            restTemplate.exchange(deleteUrl, HttpMethod.DELETE, entity, Void.class);
            
        } catch (Exception e) {
            throw new RuntimeException("Failed to delete image", e);
        }
    }
    
    private String authenticate() {
        String authUrl = registryUrl + "/v2/token";
        String credentials = username + ":" + password;
        String encodedCredentials = Base64Utils.encodeToString(credentials.getBytes());
        
        HttpHeaders headers = new HttpHeaders();
        headers.add("Authorization", "Basic " + encodedCredentials);
        
        HttpEntity<String> entity = new HttpEntity<>(headers);
        ResponseEntity<AuthResponse> response = restTemplate.exchange(
            authUrl, HttpMethod.GET, entity, AuthResponse.class);
        
        return response.getBody().getToken();
    }
    
    private HttpHeaders createAuthHeaders(String token) {
        HttpHeaders headers = new HttpHeaders();
        headers.add("Authorization", "Bearer " + token);
        return headers;
    }
    
    private String initiateUpload(String imageName, String authToken) {
        String uploadUrl = String.format("%s/v2/%s/blobs/uploads/", registryUrl, imageName);
        
        HttpHeaders headers = createAuthHeaders(authToken);
        HttpEntity<String> entity = new HttpEntity<>(headers);
        
        ResponseEntity<String> response = restTemplate.exchange(
            uploadUrl, HttpMethod.POST, entity, String.class);
        
        return response.getHeaders().getLocation().toString();
    }
    
    private void uploadImageLayers(String uploadUrl, byte[] imageData, String authToken) {
        HttpHeaders headers = createAuthHeaders(authToken);
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        
        HttpEntity<byte[]> entity = new HttpEntity<>(imageData, headers);
        restTemplate.exchange(uploadUrl, HttpMethod.PUT, entity, Void.class);
    }
    
    private String createManifest(String imageName, String tag) {
        // Create Docker manifest v2 format
        Map<String, Object> manifest = new HashMap<>();
        manifest.put("schemaVersion", 2);
        manifest.put("mediaType", "application/vnd.docker.distribution.manifest.v2+json");
        
        // Add config and layers
        return new ObjectMapper().writeValueAsString(manifest);
    }
    
    private void uploadManifest(String imageName, String tag, String manifest, String authToken) {
        String manifestUrl = String.format("%s/v2/%s/manifests/%s", registryUrl, imageName, tag);
        
        HttpHeaders headers = createAuthHeaders(authToken);
        headers.setContentType(MediaType.valueOf("application/vnd.docker.distribution.manifest.v2+json"));
        
        HttpEntity<String> entity = new HttpEntity<>(manifest, headers);
        restTemplate.exchange(manifestUrl, HttpMethod.PUT, entity, Void.class);
    }
    
    private String getManifestDigest(String imageName, String tag, String authToken) {
        String manifestUrl = String.format("%s/v2/%s/manifests/%s", registryUrl, imageName, tag);
        
        HttpHeaders headers = createAuthHeaders(authToken);
        headers.add("Accept", "application/vnd.docker.distribution.manifest.v2+json");
        
        HttpEntity<String> entity = new HttpEntity<>(headers);
        ResponseEntity<String> response = restTemplate.exchange(
            manifestUrl, HttpMethod.HEAD, entity, String.class);
        
        return response.getHeaders().getFirst("Docker-Content-Digest");
    }
    
    private ImageMetadata parseManifest(String manifestJson) {
        // Parse manifest and extract metadata
        return new ImageMetadata();
    }
    
    // DTOs
    static class AuthResponse {
        private String token;
        public String getToken() { return token; }
        public void setToken(String token) { this.token = token; }
    }
    
    static class RepositoryCatalog {
        private List<String> repositories;
        public List<String> getRepositories() { return repositories; }
        public void setRepositories(List<String> repositories) { this.repositories = repositories; }
    }
    
    static class TagsList {
        private List<String> tags;
        public List<String> getTags() { return tags; }
        public void setTags(List<String> tags) { this.tags = tags; }
    }
    
    static class ImageMetadata {
        private String digest;
        private long size;
        private Date createdAt;
        
        // Getters and setters
        public String getDigest() { return digest; }
        public void setDigest(String digest) { this.digest = digest; }
        
        public long getSize() { return size; }
        public void setSize(long size) { this.size = size; }
        
        public Date getCreatedAt() { return createdAt; }
        public void setCreatedAt(Date createdAt) { this.createdAt = createdAt; }
    }
}

Container Health Monitoring

Health Check Service

java
// ContainerHealthService.java
package com.mycompany.health;

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

@Component
public class ContainerHealthIndicator implements HealthIndicator {
    
    @Override
    public Health health() {
        Map<String, Object> details = new HashMap<>();
        
        try {
            // Check container resources
            checkMemoryUsage(details);
            checkDiskSpace(details);
            checkFileDescriptors(details);
            checkNetworkConnectivity(details);
            
            // All checks passed
            return Health.up()
                .withDetails(details)
                .build();
                
        } catch (Exception e) {
            return Health.down()
                .withException(e)
                .withDetails(details)
                .build();
        }
    }
    
    private void checkMemoryUsage(Map<String, Object> details) {
        Runtime runtime = Runtime.getRuntime();
        long totalMemory = runtime.totalMemory();
        long freeMemory = runtime.freeMemory();
        long usedMemory = totalMemory - freeMemory;
        long maxMemory = runtime.maxMemory();
        
        double memoryUsagePercent = (double) usedMemory / maxMemory * 100;
        
        details.put("memory.used", usedMemory);
        details.put("memory.total", totalMemory);
        details.put("memory.max", maxMemory);
        details.put("memory.usage.percent", Math.round(memoryUsagePercent * 100.0) / 100.0);
        
        if (memoryUsagePercent > 90) {
            throw new RuntimeException("Memory usage too high: " + memoryUsagePercent + "%");
        }
    }
    
    private void checkDiskSpace(Map<String, Object> details) throws IOException {
        Path appPath = Paths.get("/app");
        long totalSpace = Files.getFileStore(appPath).getTotalSpace();
        long usableSpace = Files.getFileStore(appPath).getUsableSpace();
        long usedSpace = totalSpace - usableSpace;
        
        double diskUsagePercent = (double) usedSpace / totalSpace * 100;
        
        details.put("disk.used", usedSpace);
        details.put("disk.total", totalSpace);
        details.put("disk.usable", usableSpace);
        details.put("disk.usage.percent", Math.round(diskUsagePercent * 100.0) / 100.0);
        
        if (diskUsagePercent > 95) {
            throw new RuntimeException("Disk usage too high: " + diskUsagePercent + "%");
        }
    }
    
    private void checkFileDescriptors(Map<String, Object> details) throws IOException {
        try {
            Path fdPath = Paths.get("/proc/self/fd");
            long openFileDescriptors = Files.list(fdPath).count();
            
            details.put("file.descriptors.open", openFileDescriptors);
            
            // Check for file descriptor leaks
            if (openFileDescriptors > 1000) {
                throw new RuntimeException("Too many open file descriptors: " + openFileDescriptors);
            }
            
        } catch (IOException e) {
            // If we can't read /proc (non-Linux), skip this check
            details.put("file.descriptors.check", "skipped");
        }
    }
    
    private void checkNetworkConnectivity(Map<String, Object> details) {
        // Check connectivity to essential services
        boolean databaseConnectable = checkService("DATABASE_HOST", 5432);
        boolean redisConnectable = checkService("REDIS_HOST", 6379);
        
        details.put("network.database.connected", databaseConnectable);
        details.put("network.redis.connected", redisConnectable);
        
        if (!databaseConnectable) {
            throw new RuntimeException("Cannot connect to database");
        }
    }
    
    private boolean checkService(String hostEnv, int port) {
        String host = System.getenv(hostEnv);
        if (host == null) {
            return true; // Skip check if not configured
        }
        
        try (java.net.Socket socket = new java.net.Socket()) {
            socket.connect(new java.net.InetSocketAddress(host, port), 5000);
            return true;
        } catch (IOException e) {
            return false;
        }
    }
}

// Container metrics service
@Component
public class ContainerMetricsService {
    
    private final MeterRegistry meterRegistry;
    private final Timer.Sample requestTimer;
    
    public ContainerMetricsService(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
        this.requestTimer = Timer.start(meterRegistry);
        
        // Register custom metrics
        registerContainerMetrics();
    }
    
    private void registerContainerMetrics() {
        // Memory usage gauge
        Gauge.builder("container.memory.usage")
            .description("Container memory usage in bytes")
            .register(meterRegistry, this, ContainerMetricsService::getMemoryUsage);
        
        // CPU usage gauge
        Gauge.builder("container.cpu.usage")
            .description("Container CPU usage percentage")
            .register(meterRegistry, this, ContainerMetricsService::getCpuUsage);
        
        // Network metrics
        Gauge.builder("container.network.connections")
            .description("Active network connections")
            .register(meterRegistry, this, ContainerMetricsService::getNetworkConnections);
    }
    
    private double getMemoryUsage(ContainerMetricsService service) {
        Runtime runtime = Runtime.getRuntime();
        return runtime.totalMemory() - runtime.freeMemory();
    }
    
    private double getCpuUsage(ContainerMetricsService service) {
        try {
            Path statPath = Paths.get("/proc/self/stat");
            if (Files.exists(statPath)) {
                String stat = Files.readString(statPath);
                String[] parts = stat.split(" ");
                long utime = Long.parseLong(parts[13]);
                long stime = Long.parseLong(parts[14]);
                return (utime + stime) / 100.0; // Convert to percentage
            }
        } catch (IOException | NumberFormatException e) {
            // Fallback for non-Linux systems
        }
        return 0.0;
    }
    
    private double getNetworkConnections(ContainerMetricsService service) {
        try {
            Path tcpPath = Paths.get("/proc/net/tcp");
            if (Files.exists(tcpPath)) {
                return Files.lines(tcpPath).count() - 1; // Subtract header line
            }
        } catch (IOException e) {
            // Fallback for non-Linux systems
        }
        return 0.0;
    }
}

Container Vulnerability Scanning

Security Scanner Service

java
// ContainerSecurityScanner.java
package com.mycompany.security.scanner;

import org.springframework.stereotype.Service;
import java.util.*;
import java.util.concurrent.CompletableFuture;

@Service
public class ContainerSecurityScanner {
    
    private final TrivyScanner trivyScanner;
    private final ClairScanner clairScanner;
    private final SecurityDatabase securityDatabase;
    
    public ContainerSecurityScanner(
            TrivyScanner trivyScanner,
            ClairScanner clairScanner,
            SecurityDatabase securityDatabase) {
        this.trivyScanner = trivyScanner;
        this.clairScanner = clairScanner;
        this.securityDatabase = securityDatabase;
    }
    
    public CompletableFuture<ScanResult> scanImage(String imageName, String tag) {
        return CompletableFuture.supplyAsync(() -> {
            try {
                // Perform parallel scanning with multiple tools
                CompletableFuture<List<Vulnerability>> trivyResults = 
                    CompletableFuture.supplyAsync(() -> trivyScanner.scan(imageName, tag));
                
                CompletableFuture<List<Vulnerability>> clairResults = 
                    CompletableFuture.supplyAsync(() -> clairScanner.scan(imageName, tag));
                
                // Wait for all scans to complete
                List<Vulnerability> allVulnerabilities = new ArrayList<>();
                allVulnerabilities.addAll(trivyResults.get());
                allVulnerabilities.addAll(clairResults.get());
                
                // Deduplicate and prioritize vulnerabilities
                List<Vulnerability> dedupedVulnerabilities = deduplicateVulnerabilities(allVulnerabilities);
                
                // Create scan result
                ScanResult result = new ScanResult();
                result.setImageName(imageName);
                result.setTag(tag);
                result.setScanDate(new Date());
                result.setVulnerabilities(dedupedVulnerabilities);
                result.setSeverityCount(calculateSeverityCount(dedupedVulnerabilities));
                result.setRiskScore(calculateRiskScore(dedupedVulnerabilities));
                
                // Store results
                securityDatabase.storeScanResult(result);
                
                return result;
                
            } catch (Exception e) {
                throw new RuntimeException("Security scan failed for " + imageName + ":" + tag, e);
            }
        });
    }
    
    private List<Vulnerability> deduplicateVulnerabilities(List<Vulnerability> vulnerabilities) {
        Map<String, Vulnerability> vulnMap = new HashMap<>();
        
        for (Vulnerability vuln : vulnerabilities) {
            String key = vuln.getCveId();
            Vulnerability existing = vulnMap.get(key);
            
            if (existing == null || vuln.getSeverity().ordinal() > existing.getSeverity().ordinal()) {
                vulnMap.put(key, vuln);
            }
        }
        
        return new ArrayList<>(vulnMap.values());
    }
    
    private Map<Severity, Integer> calculateSeverityCount(List<Vulnerability> vulnerabilities) {
        Map<Severity, Integer> severityCount = new HashMap<>();
        
        for (Vulnerability vuln : vulnerabilities) {
            severityCount.merge(vuln.getSeverity(), 1, Integer::sum);
        }
        
        return severityCount;
    }
    
    private double calculateRiskScore(List<Vulnerability> vulnerabilities) {
        double score = 0.0;
        
        for (Vulnerability vuln : vulnerabilities) {
            switch (vuln.getSeverity()) {
                case CRITICAL -> score += 10.0;
                case HIGH -> score += 7.0;
                case MEDIUM -> score += 4.0;
                case LOW -> score += 1.0;
                default -> score += 0.0;
            }
        }
        
        return Math.min(score, 100.0); // Cap at 100
    }
    
    public List<ScanResult> getImageScanHistory(String imageName) {
        return securityDatabase.getScanHistory(imageName);
    }
    
    public ComplianceReport generateComplianceReport(String imageName, String tag) {
        ScanResult latestScan = securityDatabase.getLatestScanResult(imageName, tag);
        
        ComplianceReport report = new ComplianceReport();
        report.setImageName(imageName);
        report.setTag(tag);
        report.setReportDate(new Date());
        
        // Check compliance against security policies
        boolean compliant = checkSecurityCompliance(latestScan);
        report.setCompliant(compliant);
        
        if (!compliant) {
            report.setViolations(getComplianceViolations(latestScan));
            report.setRecommendations(getSecurityRecommendations(latestScan));
        }
        
        return report;
    }
    
    private boolean checkSecurityCompliance(ScanResult scanResult) {
        Map<Severity, Integer> severityCount = scanResult.getSeverityCount();
        
        // Policy: No critical vulnerabilities allowed
        if (severityCount.getOrDefault(Severity.CRITICAL, 0) > 0) {
            return false;
        }
        
        // Policy: Maximum 5 high severity vulnerabilities
        if (severityCount.getOrDefault(Severity.HIGH, 0) > 5) {
            return false;
        }
        
        // Policy: Risk score must be below 50
        if (scanResult.getRiskScore() >= 50.0) {
            return false;
        }
        
        return true;
    }
    
    private List<String> getComplianceViolations(ScanResult scanResult) {
        List<String> violations = new ArrayList<>();
        Map<Severity, Integer> severityCount = scanResult.getSeverityCount();
        
        int criticalCount = severityCount.getOrDefault(Severity.CRITICAL, 0);
        if (criticalCount > 0) {
            violations.add("Found " + criticalCount + " critical vulnerabilities");
        }
        
        int highCount = severityCount.getOrDefault(Severity.HIGH, 0);
        if (highCount > 5) {
            violations.add("Found " + highCount + " high severity vulnerabilities (max allowed: 5)");
        }
        
        if (scanResult.getRiskScore() >= 50.0) {
            violations.add("Risk score " + scanResult.getRiskScore() + " exceeds threshold (50.0)");
        }
        
        return violations;
    }
    
    private List<String> getSecurityRecommendations(ScanResult scanResult) {
        List<String> recommendations = new ArrayList<>();
        
        recommendations.add("Update base image to latest security-patched version");
        recommendations.add("Remove unnecessary packages and dependencies");
        recommendations.add("Apply security patches for identified vulnerabilities");
        recommendations.add("Use multi-stage builds to reduce attack surface");
        recommendations.add("Run containers with non-root user");
        
        return recommendations;
    }
    
    // DTOs
    public static class ScanResult {
        private String imageName;
        private String tag;
        private Date scanDate;
        private List<Vulnerability> vulnerabilities;
        private Map<Severity, Integer> severityCount;
        private double riskScore;
        
        // Getters and setters
        public String getImageName() { return imageName; }
        public void setImageName(String imageName) { this.imageName = imageName; }
        
        public String getTag() { return tag; }
        public void setTag(String tag) { this.tag = tag; }
        
        public Date getScanDate() { return scanDate; }
        public void setScanDate(Date scanDate) { this.scanDate = scanDate; }
        
        public List<Vulnerability> getVulnerabilities() { return vulnerabilities; }
        public void setVulnerabilities(List<Vulnerability> vulnerabilities) { this.vulnerabilities = vulnerabilities; }
        
        public Map<Severity, Integer> getSeverityCount() { return severityCount; }
        public void setSeverityCount(Map<Severity, Integer> severityCount) { this.severityCount = severityCount; }
        
        public double getRiskScore() { return riskScore; }
        public void setRiskScore(double riskScore) { this.riskScore = riskScore; }
    }
    
    public static class Vulnerability {
        private String cveId;
        private String packageName;
        private String installedVersion;
        private String fixedVersion;
        private Severity severity;
        private String description;
        private double cvssScore;
        
        // Getters and setters
        public String getCveId() { return cveId; }
        public void setCveId(String cveId) { this.cveId = cveId; }
        
        public String getPackageName() { return packageName; }
        public void setPackageName(String packageName) { this.packageName = packageName; }
        
        public String getInstalledVersion() { return installedVersion; }
        public void setInstalledVersion(String installedVersion) { this.installedVersion = installedVersion; }
        
        public String getFixedVersion() { return fixedVersion; }
        public void setFixedVersion(String fixedVersion) { this.fixedVersion = fixedVersion; }
        
        public Severity getSeverity() { return severity; }
        public void setSeverity(Severity severity) { this.severity = severity; }
        
        public String getDescription() { return description; }
        public void setDescription(String description) { this.description = description; }
        
        public double getCvssScore() { return cvssScore; }
        public void setCvssScore(double cvssScore) { this.cvssScore = cvssScore; }
    }
    
    public enum Severity {
        UNKNOWN, LOW, MEDIUM, HIGH, CRITICAL
    }
    
    public static class ComplianceReport {
        private String imageName;
        private String tag;
        private Date reportDate;
        private boolean compliant;
        private List<String> violations;
        private List<String> recommendations;
        
        // Getters and setters
        public String getImageName() { return imageName; }
        public void setImageName(String imageName) { this.imageName = imageName; }
        
        public String getTag() { return tag; }
        public void setTag(String tag) { this.tag = tag; }
        
        public Date getReportDate() { return reportDate; }
        public void setReportDate(Date reportDate) { this.reportDate = reportDate; }
        
        public boolean isCompliant() { return compliant; }
        public void setCompliant(boolean compliant) { this.compliant = compliant; }
        
        public List<String> getViolations() { return violations; }
        public void setViolations(List<String> violations) { this.violations = violations; }
        
        public List<String> getRecommendations() { return recommendations; }
        public void setRecommendations(List<String> recommendations) { this.recommendations = recommendations; }
    }
}

Build and Deployment Scripts

Makefile for Container Operations

makefile
# Makefile
.PHONY: build push deploy clean test security-scan

APP_NAME = my-web-app
VERSION = $(shell git describe --tags --always --dirty)
REGISTRY = myregistry.azurecr.io
IMAGE = $(REGISTRY)/$(APP_NAME):$(VERSION)
LATEST_IMAGE = $(REGISTRY)/$(APP_NAME):latest

# Build application
build:
	@echo "Building application..."
	./mvnw clean package -DskipTests
	docker build -t $(IMAGE) .
	docker tag $(IMAGE) $(LATEST_IMAGE)

# Build with tests
build-with-tests:
	@echo "Building application with tests..."
	./mvnw clean package
	docker build -t $(IMAGE) .
	docker tag $(IMAGE) $(LATEST_IMAGE)

# Push to registry
push: build
	@echo "Pushing image to registry..."
	docker push $(IMAGE)
	docker push $(LATEST_IMAGE)

# Security scan
security-scan:
	@echo "Running security scan..."
	trivy image --exit-code 1 --severity HIGH,CRITICAL $(IMAGE)

# Run locally with docker-compose
run-local:
	@echo "Starting local environment..."
	docker-compose up -d

# Run tests
test:
	@echo "Running tests..."
	./mvnw test

# Integration tests
test-integration:
	@echo "Running integration tests..."
	docker-compose -f docker-compose.test.yml up --abort-on-container-exit

# Clean up
clean:
	@echo "Cleaning up..."
	docker-compose down -v
	docker system prune -f
	./mvnw clean

# Deploy to staging
deploy-staging: build push
	@echo "Deploying to staging..."
	kubectl apply -f k8s/staging/

# Deploy to production
deploy-production: security-scan
	@echo "Deploying to production..."
	kubectl apply -f k8s/production/

# Health check
health-check:
	@echo "Checking application health..."
	curl -f http://localhost:8080/actuator/health

# Monitor logs
logs:
	docker-compose logs -f web-app

# Database migration
migrate:
	@echo "Running database migration..."
	docker-compose exec web-app java -jar app.jar --spring.profiles.active=migration

This comprehensive guide covers all essential aspects of container technology with Docker, including best practices, security implementations, monitoring, and deployment strategies. Use these patterns to build robust, secure, and scalable containerized applications.

Created by Eren Demir.