Skip to content

Rails開発者のためのDocker最適化テクニック集

はじめに

Dockerは現代のRails開発において不可欠なツールとなっていますが、適切な最適化を行わないと、開発効率の低下や本番環境でのパフォーマンス問題を引き起こす可能性があります。本記事では、Rails開発者向けのDocker最適化テクニックを実践的な観点から解説します。

最適化の目標

  • 開発効率: 高速なビルドとコンテナ起動
  • 本番性能: 軽量で高速なイメージ
  • セキュリティ: 最小限の攻撃面
  • 保守性: 理解しやすく管理しやすい構成

Dockerfileの最適化

1. マルチステージビルドの活用

dockerfile
# Dockerfile.optimized
# ベースイメージの指定
ARG RUBY_VERSION=3.2.0
ARG DISTRO_NAME=bullseye

###########################################
# Stage: dependencies
###########################################
FROM ruby:$RUBY_VERSION-slim-$DISTRO_NAME as dependencies

# 必要なパッケージのインストール
RUN apt-get update -qq && \
    apt-get install -yq --no-install-recommends \
      build-essential \
      gnupg2 \
      curl \
      less \
      git \
      libpq-dev \
      libvips \
      pkg-config && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# Node.js 18のインストール
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - && \
    apt-get install -y nodejs

# Yarnのインストール
RUN npm install -g yarn

###########################################
# Stage: gems
###########################################
FROM dependencies as gems

WORKDIR /app

# Gemfileのコピーと依存関係のインストール
COPY Gemfile Gemfile.lock ./
RUN bundle config set --local deployment true && \
    bundle config set --local without 'development test' && \
    bundle config set --local path vendor/bundle && \
    bundle install --jobs 4 --retry 3 && \
    bundle clean

###########################################
# Stage: node_modules
###########################################
FROM dependencies as node_modules

WORKDIR /app

# package.jsonのコピーとNode.js依存関係のインストール
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile --production=true && \
    yarn cache clean

###########################################
# Stage: assets
###########################################
FROM gems as assets

WORKDIR /app

# 本番用の設定
ENV RAILS_ENV=production
ENV NODE_ENV=production

# Node.js依存関係のコピー
COPY --from=node_modules /app/node_modules ./node_modules

# アプリケーションコードのコピー
COPY . .

# アセットのプリコンパイル
RUN RAILS_ENV=production \
    SECRET_KEY_BASE=dummy \
    bundle exec rails assets:precompile && \
    rm -rf node_modules tmp/cache

###########################################
# Stage: production (final)
###########################################
FROM ruby:$RUBY_VERSION-slim-$DISTRO_NAME as production

# 本番用ユーザーの作成
RUN groupadd --gid 1000 rails && \
    useradd --uid 1000 --gid rails --shell /bin/bash --create-home rails

# 必要最小限のランタイム依存関係のみインストール
RUN apt-get update -qq && \
    apt-get install -yq --no-install-recommends \
      postgresql-client \
      libvips \
      curl \
      tini && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

WORKDIR /app

# Gemとアセットのコピー
COPY --from=gems --chown=rails:rails /app/vendor/bundle ./vendor/bundle
COPY --from=assets --chown=rails:rails /app/public/assets ./public/assets
COPY --from=assets --chown=rails:rails /app/public/packs ./public/packs

# アプリケーションコードのコピー
COPY --chown=rails:rails . .

# Bundler設定
RUN bundle config set --local deployment true && \
    bundle config set --local without 'development test' && \
    bundle config set --local path vendor/bundle

USER rails

# ポート設定
EXPOSE 3000

# ヘルスチェック
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

# エントリーポイント
ENTRYPOINT ["tini", "--"]
CMD ["bundle", "exec", "rails", "server", "-b", "0.0.0.0"]

2. 開発用Dockerfileの最適化

dockerfile
# Dockerfile.development
ARG RUBY_VERSION=3.2.0
ARG DISTRO_NAME=bullseye

FROM ruby:$RUBY_VERSION-slim-$DISTRO_NAME

# 開発用パッケージのインストール
RUN apt-get update -qq && \
    apt-get install -yq --no-install-recommends \
      build-essential \
      gnupg2 \
      curl \
      less \
      git \
      libpq-dev \
      libvips \
      pkg-config \
      vim \
      postgresql-client \
      default-mysql-client && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# Node.js 18のインストール
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - && \
    apt-get install -y nodejs

# Yarnのインストール
RUN npm install -g yarn

# 開発用ユーザーの作成(ホストユーザーと同じUID/GID)
ARG USER_ID=1000
ARG GROUP_ID=1000
RUN groupadd --gid $GROUP_ID dev && \
    useradd --uid $USER_ID --gid dev --shell /bin/bash --create-home dev

WORKDIR /app

# 依存関係のキャッシュ最適化
COPY Gemfile Gemfile.lock ./
RUN bundle config set --local path vendor/bundle && \
    bundle install --jobs 4

COPY package.json yarn.lock ./
RUN yarn install

USER dev

# 開発サーバーの起動
CMD ["bundle", "exec", "rails", "server", "-b", "0.0.0.0"]

Docker Composeの最適化

1. 効率的な開発環境

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

services:
  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: myapp_development
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 30s
      timeout: 10s
      retries: 3

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    command: redis-server --appendonly yes
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 30s
      timeout: 3s
      retries: 3

  web:
    build:
      context: .
      dockerfile: Dockerfile.development
      args:
        USER_ID: ${USER_ID:-1000}
        GROUP_ID: ${GROUP_ID:-1000}
    ports:
      - "3000:3000"
    volumes:
      # ソースコードのマウント
      - .:/app
      # Gemキャッシュの永続化
      - bundle_cache:/app/vendor/bundle
      # Node.jsキャッシュの永続化
      - node_modules_cache:/app/node_modules
      # 一時ファイルのマウント
      - tmp_cache:/app/tmp
    environment:
      - DATABASE_URL=postgres://postgres:password@db:5432/myapp_development
      - REDIS_URL=redis://redis:6379/0
      - RAILS_ENV=development
      - BOOTSNAP_CACHE_DIR=/app/tmp/cache
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    stdin_open: true
    tty: true

  sidekiq:
    build:
      context: .
      dockerfile: Dockerfile.development
      args:
        USER_ID: ${USER_ID:-1000}
        GROUP_ID: ${GROUP_ID:-1000}
    volumes:
      - .:/app
      - bundle_cache:/app/vendor/bundle
    environment:
      - DATABASE_URL=postgres://postgres:password@db:5432/myapp_development
      - REDIS_URL=redis://redis:6379/0
      - RAILS_ENV=development
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    command: bundle exec sidekiq

  webpack:
    build:
      context: .
      dockerfile: Dockerfile.development
      args:
        USER_ID: ${USER_ID:-1000}
        GROUP_ID: ${GROUP_ID:-1000}
    volumes:
      - .:/app
      - node_modules_cache:/app/node_modules
    environment:
      - NODE_ENV=development
      - RAILS_ENV=development
    command: yarn build --watch
    ports:
      - "3035:3035"

volumes:
  postgres_data:
  redis_data:
  bundle_cache:
  node_modules_cache:
  tmp_cache:

networks:
  default:
    name: myapp_network

2. 本番環境用の構成

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

services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
      target: production
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=${DATABASE_URL}
      - REDIS_URL=${REDIS_URL}
      - RAILS_ENV=production
      - RAILS_SERVE_STATIC_FILES=true
      - RAILS_LOG_TO_STDOUT=true
    volumes:
      - /app/storage
    deploy:
      replicas: 2
      resources:
        limits:
          memory: 512M
        reservations:
          memory: 256M
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  sidekiq:
    build:
      context: .
      dockerfile: Dockerfile
      target: production
    environment:
      - DATABASE_URL=${DATABASE_URL}
      - REDIS_URL=${REDIS_URL}
      - RAILS_ENV=production
    volumes:
      - /app/storage
    command: bundle exec sidekiq
    deploy:
      replicas: 1
      resources:
        limits:
          memory: 256M
        reservations:
          memory: 128M
      restart_policy:
        condition: on-failure

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./ssl:/etc/nginx/ssl:ro
    depends_on:
      - web
    deploy:
      resources:
        limits:
          memory: 64M

パフォーマンス最適化

1. イメージサイズの最小化

dockerfile
# .dockerignore
# バージョン管理
.git
.gitignore
README.md
Dockerfile*
docker-compose*

# 開発ファイル
.env.development
.env.test
coverage/
spec/
test/

# ログとキャッシュ
log/*
tmp/*
!tmp/.keep

# Node.js
node_modules/
npm-debug.log
yarn-error.log

# その他
.DS_Store
*.swp
*.swo
bash
#!/bin/bash
# scripts/optimize_image.sh

echo "Analyzing Docker image size..."

# イメージサイズの確認
docker images | grep myapp

echo "Analyzing layers..."
docker history myapp:latest --human --format "table {{.CreatedBy}}\t{{.Size}}"

echo "Running dive analysis..."
# diveツールを使用してレイヤーの詳細分析
if command -v dive &> /dev/null; then
    dive myapp:latest
else
    echo "Install dive for detailed layer analysis: https://github.com/wagoodman/dive"
fi

2. ビルドキャッシュの最適化

bash
#!/bin/bash
# scripts/build_optimized.sh

# ビルドキャッシュを活用した効率的なビルド

echo "Building with optimized caching..."

# 開発環境
docker build \
  --file Dockerfile.development \
  --target dependencies \
  --cache-from myapp:dependencies \
  --tag myapp:dependencies \
  .

docker build \
  --file Dockerfile.development \
  --cache-from myapp:dependencies \
  --cache-from myapp:development \
  --tag myapp:development \
  .

# 本番環境
docker build \
  --file Dockerfile \
  --target dependencies \
  --cache-from myapp:dependencies \
  --tag myapp:dependencies \
  .

docker build \
  --file Dockerfile \
  --target gems \
  --cache-from myapp:dependencies \
  --cache-from myapp:gems \
  --tag myapp:gems \
  .

docker build \
  --file Dockerfile \
  --cache-from myapp:dependencies \
  --cache-from myapp:gems \
  --cache-from myapp:production \
  --tag myapp:production \
  .

echo "Build completed with optimized caching."

3. メモリとCPU使用量の最適化

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

services:
  web:
    # リソース制限
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 512M
        reservations:
          cpus: '0.5'
          memory: 256M
    
    # JVMのようなメモリ設定が必要な場合
    environment:
      - MALLOC_ARENA_MAX=2
      - RUBY_GC_HEAP_INIT_SLOTS=10000
      - RUBY_GC_HEAP_FREE_SLOTS=4000
      - RUBY_GC_HEAP_GROWTH_FACTOR=1.1
      - RUBY_GC_HEAP_GROWTH_MAX_SLOTS=10000

セキュリティ強化

1. セキュアなDockerfile

dockerfile
# Dockerfile.secure
FROM ruby:3.2.0-slim-bullseye

# セキュリティアップデートの適用
RUN apt-get update && \
    apt-get upgrade -y && \
    apt-get install -yq --no-install-recommends \
      # 必要最小限のパッケージのみ
      libpq5 \
      libvips42 \
      ca-certificates && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# 非rootユーザーの作成
RUN groupadd --gid 10001 app && \
    useradd --uid 10001 --gid app --shell /bin/bash --create-home app

# セキュリティ関連の設定
RUN mkdir -p /app && \
    chown -R app:app /app

WORKDIR /app

# Gemのコピー(rootで実行)
COPY --chown=app:app Gemfile Gemfile.lock ./

# 非rootユーザーに切り替え
USER app

# Bundle設定
RUN bundle config set --local deployment true && \
    bundle config set --local without 'development test' && \
    bundle config set --local path vendor/bundle

# アプリケーションファイルのコピー
COPY --chown=app:app . .

# 読み取り専用ファイルシステムのための準備
RUN mkdir -p tmp/pids tmp/cache tmp/sockets

# ヘルスチェック用エンドポイントの設定
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

# 必要なポートのみ公開
EXPOSE 3000

# セキュアなエントリーポイント
ENTRYPOINT ["bundle", "exec"]
CMD ["rails", "server", "-b", "0.0.0.0"]

2. セキュリティスキャン

bash
#!/bin/bash
# scripts/security_scan.sh

echo "Running security scans..."

# Trivyを使用した脆弱性スキャン
if command -v trivy &> /dev/null; then
    echo "Scanning with Trivy..."
    trivy image myapp:latest
else
    echo "Trivy not found. Install: https://github.com/aquasecurity/trivy"
fi

# Docker Benchmarkテスト
if command -v docker-bench-security &> /dev/null; then
    echo "Running Docker Benchmark..."
    docker-bench-security
else
    echo "Docker Bench not found. Install: https://github.com/docker/docker-bench-security"
fi

# Hadolintを使用したDockerfile静的解析
if command -v hadolint &> /dev/null; then
    echo "Analyzing Dockerfile with hadolint..."
    hadolint Dockerfile
else
    echo "Hadolint not found. Install: https://github.com/hadolint/hadolint"
fi

開発ワークフローの最適化

1. 高速開発環境

bash
#!/bin/bash
# scripts/dev_setup.sh

# 開発環境の高速セットアップ

echo "Setting up optimized development environment..."

# 環境変数の設定
export USER_ID=$(id -u)
export GROUP_ID=$(id -g)

# 既存コンテナの停止
docker-compose down

# ボリュームの作成(初回のみ)
docker volume create myapp_bundle_cache
docker volume create myapp_node_modules_cache

# 依存関係のプリビルド
docker-compose build --parallel

# データベースの初期化
docker-compose up -d db redis
docker-compose run --rm web bundle exec rails db:create db:migrate

# 開発サーバーの起動
docker-compose up

2. ホットリロード対応

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

services:
  web:
    volumes:
      # ソースコードのリアルタイム同期
      - .:/app:cached
      # Bundlerキャッシュの永続化
      - bundle_cache:/app/vendor/bundle
      # Node.jsキャッシュの永続化
      - node_modules_cache:/app/node_modules
      # ログの永続化
      - ./log:/app/log
    environment:
      # 開発用の設定
      - RAILS_ENV=development
      - RACK_ENV=development
      - WEB_CONCURRENCY=1
      - RAILS_MAX_THREADS=5
      # ホットリロード用
      - LISTEN_USE_POLLING=true
      - SPRING_SOCKET=/app/tmp/spring.sock
    ports:
      - "3000:3000"
      - "1234:1234"  # デバッガーポート

  webpack:
    environment:
      - WEBPACKER_DEV_SERVER_HOST=0.0.0.0
      - WEBPACKER_DEV_SERVER_PUBLIC=localhost:3035
    ports:
      - "3035:3035"

モニタリングとデバッグ

1. コンテナモニタリング

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

services:
  prometheus:
    image: prom/prometheus:latest
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3001:3000"
    volumes:
      - grafana_data:/var/lib/grafana
      - ./grafana/dashboards:/etc/grafana/provisioning/dashboards
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin

  cadvisor:
    image: gcr.io/cadvisor/cadvisor:latest
    ports:
      - "8080:8080"
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:rw
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro

volumes:
  prometheus_data:
  grafana_data:

2. ログ管理

yaml
# logging配置
version: '3.8'

services:
  web:
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
        labels: "service=web"
    
  fluentd:
    image: fluent/fluentd:latest
    ports:
      - "24224:24224"
    volumes:
      - ./fluentd.conf:/fluentd/etc/fluent.conf
      - log_data:/var/log/fluentd

volumes:
  log_data:

CI/CDでの最適化

yaml
# .github/workflows/docker.yml
name: Docker Build and Deploy

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v2
    
    - name: Login to DockerHub
      uses: docker/login-action@v2
      with:
        username: ${{ secrets.DOCKERHUB_USERNAME }}
        password: ${{ secrets.DOCKERHUB_TOKEN }}
    
    - name: Build and push
      uses: docker/build-push-action@v4
      with:
        context: .
        push: true
        tags: myapp:latest
        cache-from: type=gha
        cache-to: type=gha,mode=max
        platforms: linux/amd64,linux/arm64

まとめ

Dockerを効果的に活用することで、Rails開発の生産性と本番環境の安定性を大幅に向上させることができます。重要なのは、開発フェーズに応じた適切な最適化を行うことです。

重要なポイント:

  • マルチステージビルドによるイメージサイズの最小化
  • 効果的なキャッシュ戦略
  • セキュリティを考慮した設定
  • 開発効率を高めるワークフロー
  • 継続的なモニタリングと改善

これらのテクニックを段階的に導入し、チームの開発効率とアプリケーションの品質向上を実現しましょう。

AI が自動生成した技術記事をまとめたテックブログ