Docker:開発から本番環境まで

アプリケーションのコンテナ化のための完全なDockerガイド。Dockerfile、Docker Compose、マルチステージビルド、本番環境へのデプロイを実践的な例で解説します。

開発から本番環境までのDockerガイド

Dockerはアプリケーションの開発、テスト、デプロイの方法を根本的に変革しています。アプリケーションとその依存関係をポータブルなコンテナにカプセル化することで、Dockerは有名な「自分のマシンでは動く」という問題を排除し、すべての環境での一貫性を保証します。このガイドでは、最初のDockerfileから本番環境へのデプロイまでの完全な道のりを解説します。

2026年のDocker

Docker Desktop 5.xは、ネイティブcontainerdサポート、最適化されたリソース管理、シームレスなKubernetes統合など、大幅なパフォーマンス改善をもたらしています。マルチアーキテクチャイメージ(ARM/x86)は現在標準的なプラクティスとなっています。

コンテナ化の基礎

コンテナは、コード、ランタイム、システムライブラリ、設定をパッケージ化する軽量なソフトウェアユニットです。ハードウェアを仮想化する仮想マシンとは異なり、コンテナはホストシステムのカーネルを共有するため、起動が高速でリソース消費が少なくなります。

bash
# terminal
# Docker installation on Ubuntu
sudo apt update
sudo apt install -y docker.io

# Add user to docker group (avoids sudo)
sudo usermod -aG docker $USER

# Verify installation
docker --version
# Docker version 26.1.0, build 1234567

# First container: downloads image and runs
docker run hello-world

このコマンドはDocker Hubからhello-worldイメージをダウンロードし、確認メッセージを表示するコンテナを起動します。

bash
# terminal
# List running containers
docker ps

# List all containers (including stopped)
docker ps -a

# List downloaded images
docker images

# Remove a container
docker rm <container_id>

# Remove an image
docker rmi <image_name>

これらの基本コマンドでコンテナとイメージのライフサイクルを管理します。

最初のDockerfileの作成

DockerfileにはDockerイメージを構築するための命令が含まれています。各命令は最終イメージにレイヤーを作成し、キャッシュと再利用を可能にします。

dockerfile
# Dockerfile
# Base image: Node.js 22 on Alpine Linux (lightweight)
FROM node:22-alpine

# Set working directory in the container
WORKDIR /app

# Copy dependency files first (cache optimization)
COPY package*.json ./

# Install dependencies
RUN npm ci --only=production

# Copy source code
COPY . .

# Expose port (documentation)
EXPOSE 3000

# Startup command
CMD ["node", "server.js"]

命令の順序はキャッシュの最適化にとって極めて重要です。変更頻度の低いファイル(package.json)はソースコードよりも先にコピーする必要があります。

bash
# terminal
# Build image with a tag
docker build -t my-app:1.0 .

# Run the container
docker run -d -p 3000:3000 --name my-app-container my-app:1.0

# Check logs
docker logs my-app-container

# Access container shell
docker exec -it my-app-container sh

-dフラグはコンテナをバックグラウンドで実行し、-pはコンテナのポート3000をホストのポート3000にマッピングします。

Alpine vs Debian

Alpineイメージは大幅に小さくなっています(Debianの約120MBに対して約5MB)。ただし、glibcの代わりにmusl libcを使用しているため、一部のネイティブ依存関係で非互換性が発生する可能性があります。問題が発生した場合は、Debianベースのイメージ(node:22-slim)の使用が推奨されます。

本番環境のためのマルチステージビルド

マルチステージビルドは、ビルド環境をランタイム環境から分離することで、最適化された本番用イメージを作成します。最終イメージには必要なアーティファクトのみが含まれます。

dockerfile
# Dockerfile.production
# ============================================
# Stage 1: Build
# ============================================
FROM node:22-alpine AS builder

WORKDIR /app

# Copy and install dependencies (including devDependencies)
COPY package*.json ./
RUN npm ci

# Copy source code
COPY . .

# Build the application (TypeScript, bundling, etc.)
RUN npm run build

# ============================================
# Stage 2: Production
# ============================================
FROM node:22-alpine AS production

# Non-root user for security
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001

WORKDIR /app

# Copy only necessary files from builder stage
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./

# Switch to non-root user
USER nodejs

# Environment variables
ENV NODE_ENV=production
ENV PORT=3000

EXPOSE 3000

# Startup command
CMD ["node", "dist/server.js"]

このアプローチにより、ビルドツール、devDependencies、ソースファイルを除外し、最終イメージのサイズを大幅に削減できます。

bash
# terminal
# Build with specific file
docker build -f Dockerfile.production -t my-app:production .

# Compare image sizes
docker images | grep my-app
# my-app    production    abc123    150MB
# my-app    1.0           def456    450MB

サイズ削減はプロジェクトによって60〜70%に達し、デプロイ時間の改善と攻撃対象領域の縮小につながります。

ローカルオーケストレーションのためのDocker Compose

Docker Composeはマルチコンテナアプリケーションの管理を簡素化します。YAMLファイルですべてのサービス、設定、依存関係を宣言します。

yaml
# docker-compose.yml
version: "3.9"

services:
  # Main application
  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=development
      - DATABASE_URL=postgresql://postgres:secret@db:5432/myapp
      - REDIS_URL=redis://cache:6379
    volumes:
      # Mount source code for hot-reload
      - ./src:/app/src
      - ./package.json:/app/package.json
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started
    networks:
      - app-network

  # PostgreSQL database
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: myapp
    volumes:
      # Data persistence
      - postgres_data:/var/lib/postgresql/data
      # Initialization script
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5
    networks:
      - app-network

  # Redis cache
  cache:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    command: redis-server --appendonly yes
    networks:
      - app-network

# Named volumes for persistence
volumes:
  postgres_data:
  redis_data:

# Dedicated network for isolation
networks:
  app-network:
    driver: bridge

サービスはDockerの内部ネットワークを通じて名前(db、cache)で通信します。ヘルスチェックにより、アプリケーション起動前に依存関係の準備が完了していることが保証されます。

bash
# terminal
# Start all services
docker compose up -d

# View logs from all services
docker compose logs -f

# Logs from a specific service
docker compose logs -f app

# Stop and remove containers
docker compose down

# Removal including volumes (caution: data loss)
docker compose down -v

# Rebuild after Dockerfile changes
docker compose up -d --build

DevOpsの面接対策はできていますか?

インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。

シークレットと環境変数の管理

シークレットの安全な管理は本番環境において極めて重要です。Dockerはコンテキストに応じていくつかのアプローチを提供しています。

yaml
# docker-compose.override.yml (development only)
version: "3.9"

services:
  app:
    env_file:
      - .env.development
    environment:
      - DEBUG=true

本番環境では、Docker secretsがより高いセキュリティを提供します。

yaml
# docker-compose.production.yml
version: "3.9"

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile.production
    secrets:
      - db_password
      - api_key
    environment:
      - NODE_ENV=production
      - DATABASE_PASSWORD_FILE=/run/secrets/db_password
      - API_KEY_FILE=/run/secrets/api_key

secrets:
  db_password:
    file: ./secrets/db_password.txt
  api_key:
    file: ./secrets/api_key.txt

アプリケーションコードはマウントされたファイルからシークレットを読み取ります。

config/secrets.jsjavascript
const fs = require('fs');
const path = require('path');

// Utility function to read Docker secrets
function readSecret(secretName) {
  const secretPath = `/run/secrets/${secretName}`;

  // Check if secret file exists
  if (fs.existsSync(secretPath)) {
    return fs.readFileSync(secretPath, 'utf8').trim();
  }

  // Fallback to classic environment variables
  const envVar = secretName.toUpperCase();
  return process.env[envVar];
}

module.exports = {
  databasePassword: readSecret('db_password'),
  apiKey: readSecret('api_key'),
};

このアプローチにより、環境変数やDockerイメージでシークレットが露出することを防ぎます。

Dockerイメージの最適化

いくつかのテクニックによりイメージサイズを削減し、パフォーマンスを向上させることができます。

dockerfile
# Dockerfile.optimized
FROM node:22-alpine AS base

# Install necessary tools in a single layer
RUN apk add --no-cache \
    dumb-init \
    && rm -rf /var/cache/apk/*

# ============================================
# Stage: Dependencies
# ============================================
FROM base AS deps

WORKDIR /app

# Copy only lock files for caching
COPY package.json package-lock.json ./

# Install with mounted npm cache (BuildKit)
RUN --mount=type=cache,target=/root/.npm \
    npm ci --only=production

# ============================================
# Stage: Builder
# ============================================
FROM base AS builder

WORKDIR /app

COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm \
    npm ci

COPY . .
RUN npm run build

# ============================================
# Stage: Production
# ============================================
FROM base AS production

# Image metadata
LABEL maintainer="team@example.com"
LABEL version="1.0"
LABEL description="Production-ready Node.js application"

# Non-root user
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001

WORKDIR /app

# Copy production dependencies
COPY --from=deps --chown=nodejs:nodejs /app/node_modules ./node_modules

# Copy build output
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
COPY --from=builder --chown=nodejs:nodejs /app/package.json ./

USER nodejs

ENV NODE_ENV=production

# dumb-init as PID 1 for signal handling
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "dist/server.js"]

dumb-initを使用することで、Unixシグナルの正しい処理が保証され、コンテナのグレースフルシャットダウンが可能になります。

bash
# terminal
# Enable BuildKit for advanced features
export DOCKER_BUILDKIT=1

# Build with cache and detailed output
docker build --progress=plain -t my-app:optimized .

# Analyze image layers
docker history my-app:optimized

# Detailed image inspection
docker inspect my-app:optimized
イメージのセキュリティ

イメージはTrivyやSnykなどのツールを使用して定期的に脆弱性スキャンを行う必要があります。ベースイメージはセキュリティパッチを含めるために定期的に更新する必要があります。

高度なDockerネットワーキング

Dockerは異なるユースケースに対応する複数のネットワークドライバーを提供しています。

yaml
# docker-compose.networking.yml
version: "3.9"

services:
  # Publicly accessible frontend
  frontend:
    build: ./frontend
    ports:
      - "80:80"
    networks:
      - frontend-network
      - backend-network

  # API accessible only from frontend
  api:
    build: ./api
    networks:
      - backend-network
      - database-network
    # No external ports exposed

  # Isolated database
  database:
    image: postgres:16-alpine
    networks:
      - database-network
    # Accessible only by API

networks:
  frontend-network:
    driver: bridge
  backend-network:
    driver: bridge
    internal: true  # No internet access
  database-network:
    driver: bridge
    internal: true

この設定は最小権限の原則に従ってサービスを分離します。データベースはAPIからのみアクセス可能です。

bash
# terminal
# Inspect Docker networks
docker network ls

# Details of a specific network
docker network inspect app-network

# Create a custom network
docker network create --driver bridge --subnet 172.28.0.0/16 custom-network

# Connect a container to an existing network
docker network connect custom-network my-container

ボリュームとデータの永続化

Dockerボリュームはコンテナのライフサイクルを超えてデータを保持します。

yaml
# docker-compose.volumes.yml
version: "3.9"

services:
  app:
    image: my-app:latest
    volumes:
      # Named volume for persistent data
      - app_data:/app/data
      # Bind mount for development
      - ./uploads:/app/uploads:rw
      # Read-only mount for configuration
      - ./config:/app/config:ro

  backup:
    image: alpine
    volumes:
      # Access same volume for backups
      - app_data:/data:ro
      - ./backups:/backups
    command: |
      sh -c "tar czf /backups/backup-$$(date +%Y%m%d).tar.gz /data"

volumes:
  app_data:
    driver: local
    driver_opts:
      type: none
      device: /path/to/host/data
      o: bind

名前付きボリュームとバインドマウントの区別は重要です。ボリュームはDockerが管理し、バインドマウントはホストのファイルシステムを直接使用します。

bash
# terminal
# List volumes
docker volume ls

# Inspect a volume
docker volume inspect app_data

# Remove orphaned volumes
docker volume prune

# Backup a volume
docker run --rm -v app_data:/data -v $(pwd):/backup alpine \
  tar czf /backup/volume-backup.tar.gz /data

本番環境へのデプロイ

堅牢なデプロイワークフローにはビルド、テスト、レジストリへのプッシュが含まれます。

bash
# deploy.sh
#!/bin/bash
set -e

# Variables
REGISTRY="registry.example.com"
IMAGE_NAME="my-app"
VERSION=$(git describe --tags --always)

echo "Building version: $VERSION"

# Build production image
docker build \
  -f Dockerfile.production \
  -t $REGISTRY/$IMAGE_NAME:$VERSION \
  -t $REGISTRY/$IMAGE_NAME:latest \
  --build-arg BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
  --build-arg VERSION=$VERSION \
  .

# Security scan
echo "Running security scan..."
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
  aquasec/trivy image $REGISTRY/$IMAGE_NAME:$VERSION

# Push to registry
echo "Pushing to registry..."
docker push $REGISTRY/$IMAGE_NAME:$VERSION
docker push $REGISTRY/$IMAGE_NAME:latest

echo "Deployment complete: $REGISTRY/$IMAGE_NAME:$VERSION"

サーバーへのデプロイには、専用の本番用composeファイルで設定を調整します。

yaml
# docker-compose.prod.yml
version: "3.9"

services:
  app:
    image: registry.example.com/my-app:latest
    restart: always
    deploy:
      replicas: 3
      resources:
        limits:
          cpus: "0.5"
          memory: 512M
        reservations:
          cpus: "0.25"
          memory: 256M
      update_config:
        parallelism: 1
        delay: 10s
        failure_action: rollback
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

この設定は信頼性の高いデプロイのためのリソース割り当て、更新戦略、ヘルスチェックを定義します。

bash
# terminal
# Production deployment with Docker Compose
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

# Zero-downtime update (rolling update)
docker compose -f docker-compose.yml -f docker-compose.prod.yml pull
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --no-deps app

# Rollback if issues occur
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --no-deps \
  --scale app=0 && \
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --no-deps app

コンテナのモニタリングとデバッグ

コンテナのモニタリングは本番環境において不可欠です。

bash
# terminal
# Real-time statistics for all containers
docker stats

# Statistics for a specific container with custom format
docker stats my-app --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"

# Inspect processes in a container
docker top my-app

# Real-time Docker events
docker events --filter container=my-app

# Copy files from/to a container
docker cp my-app:/app/logs/error.log ./error.log

詳細なデバッグには、いくつかのテクニックが利用可能です。

bash
# terminal
# Interactive shell in a running container
docker exec -it my-app sh

# Execute a single command
docker exec my-app cat /app/config/settings.json

# Start a container in debug mode
docker run -it --rm --entrypoint sh my-app:latest

# Inspect environment variables
docker exec my-app printenv

# Analyze logs with filters
docker logs my-app --since 1h --tail 100 | grep ERROR
yaml
# docker-compose.monitoring.yml
version: "3.9"

services:
  app:
    # ... existing configuration
    labels:
      - "prometheus.scrape=true"
      - "prometheus.port=3000"
      - "prometheus.path=/metrics"

  prometheus:
    image: prom/prometheus:latest
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.retention.time=15d'

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3001:3000"
    volumes:
      - grafana_data:/var/lib/grafana
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=secret

volumes:
  prometheus_data:
  grafana_data:

このモニタリングスタックにより、コンテナメトリクスの収集と可視化が可能になります。

まとめ

Dockerはすべての環境での一貫性を保証することで開発サイクルを変革します。コンテナ化はポータビリティ、分離性、再現性をもたらします。これらは現代のアプリケーションに不可欠な特性です。

本番環境のためのDockerチェックリスト

  • ✅ 最適化されたイメージのためのマルチステージビルド
  • ✅ コンテナ内での非rootユーザー
  • ✅ すべてのサービスにヘルスチェックを設定
  • ✅ Docker secretsまたは安全な環境変数によるシークレット管理
  • ✅ リソース制限(CPU、メモリ)の定義
  • ✅ 重要なデータの永続化のためのボリューム
  • ✅ ファイルローテーション付きの集中ロギング
  • ✅ デプロイ前のイメージセキュリティスキャン
  • ✅ ダウンタイムなしの更新戦略
  • ✅ サービス間の分離されたネットワーク

今すぐ練習を始めましょう!

面接シミュレーターと技術テストで知識をテストしましょう。

Dockerの習得は、すべての現代的な開発者にとって基本的なスキルです。ローカル環境から本番環境のデプロイまで、Dockerはワークフローを標準化し、運用を簡素化します。ここで紹介した概念は、Kubernetesと大規模なコンテナオーケストレーションを探求するための確固たる基盤を形成します。

タグ

#docker
#containerization
#devops
#docker compose
#deployment

共有

関連記事