404 Not Found

404 Not Found


nginx

Laravel本番デプロイの実践ガイド

デプロイは製品を世に送り出す最後のステップです—1つのミスが全ユーザーに500エラーページを見せることになります。

1. 学ぶこと


2. リリース夜の実話

(1) 痛み:手動デプロイで常に問題が発生する

Bobが毎回ShopMetricsをデプロイする際に行うこと:サーバーにSSH → git pull → composer install → php artisan migrate → php artisan config:cache → キューワーカーの再起動 → PHP-FPMの再起動。5台のサーバーで, この手動プロセスに40分かかります。先月, マイグレーション実行を1回忘れ, サイト全体が500エラーになり2,000ドルの損失が出ました。Charlieは言いました。「あなたは2024年のSaaS製品を1990年代の方法でデプロイしている。」

(2) Docker + CI/CDによる解決策

Dockerコンテナ化によりすべての環境が完全に一致することが保証され, CI/CDパイプラインによりデプロイが「git push」1つに削減されます—テスト, ビルド, デプロイがすべて完全に自動化されます。

TEXT
git push → GitHub Actions → テスト → Dockerビルド → デプロイ → ヘルスチェック → 完了

(3) 成果

BobがCI/CDを実装した後, デプロイ時間は40分から3分に短縮され, エラー率は10%から0%に低下し, 深夜にサーバーにSSHする必要もなくなりました。


3. Dockerコンテナ化

(1) マルチステージDockerfile

DOCKERFILE
# Dockerfile
# ステージ1:依存関係のビルド
FROM composer:2.7 AS build
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-scripts --prefer-dist --no-progress
COPY . .
RUN php artisan route:cache && \
    php artisan config:cache && \
    php artisan view:cache

# ステージ2:本番イメージ
FROM php:8.3-fpm-alpine AS production
WORKDIR /var/www/html

RUN apk add --no-cache \
    nginx \
    supervisor \
    pdo_mysql \
    gd \
    zip \
    redis

RUN docker-php-ext-install pdo_mysql gd zip opcache redis

COPY --from=build /app /var/www/html
COPY docker/opcache.ini /usr/local/etc/php/conf.d/opcache.ini
COPY docker/nginx.conf /etc/nginx/http.d/default.conf
COPY docker/supervisord.conf /etc/supervisord.conf

RUN chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache

EXPOSE 8080
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]

(2) Docker Composeオーケストレーション

YAML
# docker-compose.prod.yml
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    restart: unless-stopped
    ports:
      - "8080:8080"
    env_file:
      - .env.production
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_healthy
    volumes:
      - storage:/var/www/html/storage
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 5s
      retries: 3

  queue:
    build:
      context: .
      dockerfile: Dockerfile
    restart: unless-stopped
    command: php artisan queue:work --queue=high,default --sleep=3 --tries=3
    env_file:
      - .env.production
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_healthy
    volumes:
      - storage:/var/www/html/storage

  scheduler:
    build:
      context: .
      dockerfile: Dockerfile
    restart: unless-stopped
    command: php artisan schedule:work
    env_file:
      - .env.production
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_healthy

  mysql:
    image: mysql:8.0
    restart: unless-stopped
    environment:
      MYSQL_DATABASE: ${DB_DATABASE}
      MYSQL_USER: ${DB_USERNAME}
      MYSQL_PASSWORD: ${DB_PASSWORD}
      MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
    volumes:
      - mysql_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    restart: unless-stopped
    command: redis-server --requirepass ${REDIS_PASSWORD}
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  mysql_data:
  redis_data:
  storage:

(1) ▶ サンプル:ShopMetrics Docker開発環境

YAML
# docker-compose.yml (開発用)
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile.dev
    ports:
      - "8000:8000"
    volumes:
      - .:/var/www/html
    environment:
      DB_HOST: mysql
      REDIS_HOST: redis
    depends_on:
      - mysql
      - redis
      - mailpit

  mysql:
    image: mysql:8.0
    ports:
      - "3306:3306"
    environment:
      MYSQL_DATABASE: shopmetrics
      MYSQL_USER: shopmetrics
      MYSQL_PASSWORD: secret
      MYSQL_ROOT_PASSWORD: secret
    volumes:
      - mysql_dev:/var/lib/mysql

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

  mailpit:
    image: axllent/mailpit
    ports:
      - "1025:1025"   # SMTP
      - "8025:8025"   # Web UI

volumes:
  mysql_dev:

出力:

TEXT
CONTAINER ID   IMAGE          STATUS         PORTS
abc123         nginx:latest   Up 2 hours     0.0.0.0:80->80/tcp

4. Nginx + PHP-FPM設定

(1) Nginx設定

NGINX
# docker/nginx.conf
server {
    listen 8080;
    server_name shopmetrics.io api.shopmetrics.io;
    root /var/www/html/public;
    index index.php;

    charset utf-8;

    # セキュリティヘッダー
    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-Content-Type-Options "nosniff";
    add_header X-XSS-Protection "1; mode=block";
    add_header Referrer-Policy "strict-origin-when-cross-origin";

    # 最大アップロードサイズ
    client_max_body_size 20M;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    location ~ \.php$ {
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_hide_header X-Powered-By;

        # 大きなペイロード用のバッファ
        fastcgi_buffer_size 128k;
        fastcgi_buffers 4 256k;
        fastcgi_busy_buffers_size 256k;

        # タイムアウト
        fastcgi_connect_timeout 60s;
        fastcgi_send_timeout 60s;
        fastcgi_read_timeout 60s;
    }

    # 静的コンテンツへのアクセスを拒否
    location ~ /\.(?!well-known).* {
        deny all;
    }
}

(2) SSL設定 (Let's Encrypt)

NGINX
# リバースプロキシまたはロードバランサーでのSSLターミネーション
server {
    listen 443 ssl http2;
    server_name shopmetrics.io;

    ssl_certificate /etc/letsencrypt/live/shopmetrics.io/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/shopmetrics.io/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;

    # HSTS
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    location / {
        proxy_pass http://app:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

# HTTPからHTTPSへリダイレクト
server {
    listen 80;
    server_name shopmetrics.io;
    return 301 https://$server_name$request_uri;
}

(1) ▶ サンプル:ShopMetrics Nginxマルチテナントサブドメインルーティング

NGINX
# テナントサブドメインのルーティング
server {
    listen 443 ssl http2;
    server_name ~^(?<tenant>[^\.]+)\.shopmetrics\.io$;

    ssl_certificate /etc/letsencrypt/live/shopmetrics.io/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/shopmetrics.io/privkey.pem;

    # テナントslugをLaravelに渡す
    location / {
        proxy_pass http://app:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Tenant $tenant;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

出力:

TEXT
// 実行成功

5. CI/CDパイプライン

(1) GitHub Actionsワークフロー

YAML
# .github/workflows/deploy.yml
name: Deploy ShopMetrics

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

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  test:
    runs-on: ubuntu-latest
    services:
      mysql:
        image: mysql:8.0
        env:
          MYSQL_DATABASE: shopmetrics_test
          MYSQL_USER: test
          MYSQL_PASSWORD: test
          MYSQL_ROOT_PASSWORD: test
        ports: ['3306:3306']
      redis:
        image: redis:7-alpine
        ports: ['6379:6379']
    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
        with:
          php-version: '8.3'
          coverage: xdebug
      - name: Install dependencies
        run: composer install --no-progress --prefer-dist
      - name: Copy .env
        run: cp .env.example .env && php artisan key:generate
      - name: Run tests
        run: php artisan test --parallel --coverage-text
      - name: Check code style
        run: vendor/bin/pint --test

  build:
    needs: test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4
      - name: Log in to Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: |
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to production
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.PROD_HOST }}
          username: ${{ secrets.PROD_USER }}
          key: ${{ secrets.PROD_SSH_KEY }}
          script: |
            cd /var/www/shopmetrics
            docker compose -f docker-compose.prod.yml pull
            docker compose -f docker-compose.prod.yml up -d --remove-orphans
            docker compose -f docker-compose.prod.yml exec app php artisan migrate --force
            docker compose -f docker-compose.prod.yml exec app php artisan config:cache
            docker compose -f docker-compose.prod.yml exec app php artisan queue:restart
            sleep 5
            curl -sf http://localhost:8080/health || exit 1
            echo "Deploy successful!"

(2) デプロイフローチャート

100%
flowchart LR
    A[git push main] --> B[GitHub Actions]
    B --> C[テスト実行]
    C -->|合格| D[Dockerイメージビルド]
    C -->|失敗| E[通知 + ブロック]
    D --> F[レジストリにプッシュ]
    F --> G[本番にSSH]
    G --> H[docker compose pull]
    H --> I[docker compose up -d]
    I --> J[php artisan migrate]
    J --> K[キャッシュ + キュー再起動]
    K --> L[ヘルスチェック]
    L -->|OK| M[デプロイ完了]
    L -->|失敗| N[自動ロールバック]

(1) ▶ サンプル:ShopMetricsデプロイヘルスチェックエンドポイント

PHP
// routes/web.php
Route::get('/health', function () {
    $checks = [
        'database' => fn () => DB::connection()->getPdo() ? 'ok' : 'fail',
        'redis' => fn () => Cache::put('health_check', 'ok', 10) ? 'ok' : 'fail',
        'storage' => fn () => Storage::put('health_check', 'ok') ? 'ok' : 'fail',
    ];

    $results = collect($checks)->map(fn ($check) => $check());

    if ($results->contains('fail')) {
        return response()->json([
            'status' => 'unhealthy',
            'checks' => $results,
        ], 503);
    }

    return response()->json([
        'status' => 'healthy',
        'checks' => $results,
        'timestamp' => now()->toIso8601String(),
        'version' => config('app.version', 'unknown'),
    ]);
});

出力:

TEXT
// 実行成功

6. デプロイチェックリスト

(1) リリース前チェックリスト

ステップ コマンド/アクション 説明
1 .env設定確認 APP_ENV=production, APP_DEBUG=false
2 php artisan key:generate APP_KEYが設定されていることを確認
3 php artisan migrate --force データベースマイグレーションの実行
4 php artisan config:cache 設定のキャッシュ
5 php artisan route:cache ルートのキャッシュ
6 php artisan view:cache ビューのキャッシュ
7 php artisan storage:link ストレージリンクの作成
8 php artisan queue:restart ワーカーキューの再起動
9 パーミッション確認 storage/ + bootstrap/cache/が書き込み可能
10 ヘルスチェック /healthが200を返す

(1) ▶ サンプル:ShopMetrics自動デプロイスクリプト

BASH
#!/bin/bash
# deploy.sh — ゼロダウンタイムデプロイスクリプト
set -e

APP_DIR="/var/www/shopmetrics"
RELEASES_DIR="/var/www/releases"
CURRENT_LINK="/var/www/current"
NEW_RELEASE=$(date +%Y%m%d%H%M%S)

echo "=== Deploying ShopMetrics ==="

# 1. リリースディレクトリの作成
mkdir -p "$RELEASES_DIR/$NEW_RELEASE"
cd "$RELEASES_DIR/$NEW_RELEASE"

# 2. コードのクローン/プル
git clone --depth 1 --branch main git@github.com:bob/shopmetrics.git .
echo "→ Code pulled"

# 3. 依存関係のインストール
composer install --no-dev --optimize-autoloader --no-progress
echo "→ Dependencies installed"

# 4. 環境ファイルのコピー
cp "$APP_DIR/.env.production" .env

# 5. 最適化
php artisan config:cache
php artisan route:cache
php artisan view:cache
echo "→ Caches built"

# 6. シムリンクの切り替え (アトミック)
ln -sfn "$RELEASES_DIR/$NEW_RELEASE" "$CURRENT_LINK"
echo "→ Symlink switched"

# 7. マイグレーションの実行
cd "$CURRENT_LINK"
php artisan migrate --force
echo "→ Migrations done"

# 8. キューワーカーの再起動
php artisan queue:restart
echo "→ Queue workers restarting"

# 9. ヘルスチェック
sleep 3
if curl -sf http://localhost:8080/health > /dev/null; then
    echo "✓ Health check passed"
else
    echo "✗ Health check failed! Rolling back..."
    ln -sfn "$RELEASES_DIR/$(ls -t $RELEASES_DIR | sed -n '2p')" "$CURRENT_LINK"
    echo "✗ Rolled back"
    exit 1
fi

# 10. 古いリリースのクリーンアップ (最新5つを保持)
ls -t "$RELEASES_DIR" | tail -n +6 | xargs -r rm -rf
echo "→ Old releases cleaned"

echo "=== Deploy complete! ==="

出力:

TEXT
{"status":"ok","data":{}}

7. ゼロダウンタイムデプロイ

(1) デプロイ戦略の比較

戦略 ダウンタイム リソース使用量 複雑さ 用途
直接デプロイ 5〜30秒 低トラフィック/短時間のダウンタイムが許容される場合
Blue-Green 0 2倍 高可用性要件
ローリングアップデート 0 1.5倍 マルチインスタンスデプロイ
カナリア 0 1.1倍 大規模 / グレイスケール検証が必要

(2) Blue-Greenデプロイ

100%
flowchart LR
    subgraph Blue["Blue (現在)"]
        B1[App v1.0]
        B2[DB v1.0]
    end
    subgraph Green["Green (新規)"]
        G1[App v1.1]
        G2[DB v1.1]
    end
    LB[ロードバランサー] -->|"100%"| B1
    LB -.->|"0%"| G1
    
    LB -->|"ヘルスチェック後に切り替え"| G1
BASH
# Dockerを使用したBlue-Greenデプロイ
# 現在:blueが稼働中, greenはアイドル
docker compose -f docker-compose.blue.yml up -d
# greenのヘルスチェック
curl -sf http://green:8080/health || exit 1
# トラフィックの切り替え (nginxアップストリームを更新)
echo "server green:8080;" > /etc/nginx/conf.d/upstream.conf
nginx -s reload
# 古いblueを停止
docker compose -f docker-compose.blue.yml down

(1) ▶ サンプル:ShopMetrics GitHub Actionsゼロダウンタイムデプロイ

YAML
# .github/workflows/zero-downtime-deploy.yml
name: Zero-Downtime Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - name: Determine active slot
        uses: appleboy/ssh-action@v1
        id: slot
        with:
          host: ${{ secrets.PROD_HOST }}
          username: deploy
          key: ${{ secrets.PROD_SSH_KEY }}
          script: |
            if docker compose -f docker-compose.blue.yml ps | grep -q "Up"; then
              echo "ACTIVE=blue" >> $GITHUB_OUTPUT
              echo "DEPLOY=green" >> $GITHUB_OUTPUT
            else
              echo "ACTIVE=green" >> $GITHUB_OUTPUT
              echo "DEPLOY=blue" >> $GITHUB_OUTPUT
            fi

      - name: Deploy to inactive slot
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.PROD_HOST }}
          username: deploy
          key: ${{ secrets.PROD_SSH_KEY }}
          script: |
            DEPLOY=${{ steps.slot.outputs.DEPLOY }}
            docker compose -f docker-compose.${DEPLOY}.yml pull
            docker compose -f docker-compose.${DEPLOY}.yml up -d
            docker compose -f docker-compose.${DEPLOY}.yml exec app php artisan migrate --force
            docker compose -f docker-compose.${DEPLOY}.yml exec app php artisan config:cache

      - name: Health check & switch traffic
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.PROD_HOST }}
          username: deploy
          key: ${{ secrets.PROD_SSH_KEY }}
          script: |
            DEPLOY=${{ steps.slot.outputs.DEPLOY }}
            ACTIVE=${{ steps.slot.outputs.ACTIVE }}
            # ヘルスチェックを待機
            for i in $(seq 1 10); do
              if curl -sf http://localhost:8080/health; then break; fi
              sleep 2
            done
            # nginxアップストリームを切り替え
            echo "server ${DEPLOY}:8080;" > /etc/nginx/conf.d/upstream.conf
            nginx -s reload
            # 古いスロットを停止
            docker compose -f docker-compose.${ACTIVE}.yml down
            echo "Zero-downtime deploy complete!"

出力:

TEXT
CONTAINER ID   IMAGE          STATUS         PORTS
abc123         nginx:latest   Up 2 hours     0.0.0.0:80->80/tcp

8. 総合サンプル:ShopMetricsの完全なデプロイと設定

DOCKERFILE
# ============================================
# 総合:ShopMetrics本番Dockerfile
# 最適化を含むマルチステージビルド
# ============================================

# ステージ1:フロントエンドアセットのビルド
FROM node:20-alpine AS frontend
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY vite.config.ts tailwind.config.js postcss.config.js ./
COPY resources/ ./resources/
RUN npm run build

# ステージ2:PHP依存関係のインストール
FROM composer:2.7 AS backend
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-scripts --prefer-dist --no-progress --classmap-authoritative
COPY . .
COPY --from=frontend /app/public/build ./public/build
RUN php artisan route:cache && \
    php artisan config:cache && \
    php artisan view:cache && \
    php artisan event:cache

# ステージ3:本番イメージ
FROM php:8.3-fpm-alpine AS production
WORKDIR /var/www/html

RUN apk add --no-cache \
    curl nginx supervisor mysql-client \
    && docker-php-ext-install pdo_mysql opcache pcntl

COPY docker/opcache.ini /usr/local/etc/php/conf.d/
COPY docker/nginx.conf /etc/nginx/http.d/default.conf
COPY docker/supervisord.conf /etc/supervisord.conf
COPY docker/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

COPY --from=backend /app /var/www/html
RUN chown -R www-data:www-data storage bootstrap/cache

EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
    CMD curl -f http://localhost:8080/health || exit 1

ENTRYPOINT ["/entrypoint.sh"]
BASH
#!/bin/bash
# docker/entrypoint.sh
set -e

echo "→ Running migrations..."
php artisan migrate --force

echo "→ Starting supervisord..."
exec /usr/bin/supervisord -c /etc/supervisord.conf
INI
; docker/supervisord.conf
[supervisord]
nodaemon=true
user=root

[program:php-fpm]
command=php-fpm --nodaemonize
autorestart=true

[program:nginx]
command=nginx -g 'daemon off;'
autorestart=true

[program:queue-worker]
command=php artisan queue:work --queue=high,default --sleep=3 --tries=3 --max-time=3600
autorestart=true
user=www-data
numprocs=2
process_name=%(program_name)s_%(process_num)02d

❓ よくある質問

Q Dockerと従来型デプロイのどちらが良いですか?
A Dockerの利点は環境の一貫性, 再現可能なデプロイ, スケーリングの容易さです。欠点は学習コストとわずかなパフォーマンスオーバーヘッド (<5%)です。新規プロジェクトにはDockerをお勧めします。従来型デプロイの既存プロジェクトは段階的な移行を推奨します。
Q SSL証明書はどのように管理しますか?
A Let's Encrypt + Certbotで自動更新を使用します。またはクラウドプラットフォーム (AWS ALB/Cloudflare)でSSLをオフにし, バックエンドではHTTPのみを使用します。証明書の手動管理はしないでください。
Q 本番環境でのデータベースマイグレーションは安全ですか?
A カラムやインデックスの追加は安全です。カラムの削除や型の変更は, 先に新しいカラムを追加 → データを移行 → 古いカラムを削除する必要があります。CIでphp artisan migrate --forceを自動化し, マイグレーションが失敗した場合はデプロイをブロックします。
Q Dockerイメージが大きすぎる場合はどうすればよいですか?
A Alpineベースイメージを使用する, マルチステージビルド (node_modules/.gitをコピーしない), composer install--no-devを指定する, .dockerignoreでテストファイルを除外してください。目標サイズ:< 200MB。
Q CI/CDパイプラインでロールバックはどう処理しますか?
A 直近5バージョンのDockerイメージを保持します。ロールバックはdocker compose pull <previous-version>docker compose up -dを組み合わせるだけです。Blue-Greenデプロイの方がロールバックがさらに速い—古いスロットに切り替えるだけです。
Q queue:restartでタスクが失われますか?
A いいえ。queue:restartはワーカーが現在のタスクを完了した後にグレースフル終了し, Supervisorが新しいワーカーを自動的に再起動します。まだ開始されていないタスクはキューに残り, 新しいワーカーによる処理を待ちます。

📖 まとめ


📝 練習問題

  1. 基本問題 (⭐):ShopMetricsのDocker Compose開発環境 (app + MySQL + Redis + Mailpit)を構築し, docker compose up -dの実行後すぐにアプリケーションにアクセスできることを確認してください。

  2. 応用問題 (⭐⭐):mainブランチへのプッシュ時に自動実行されるGitHub Actionsワークフローを記述してください:テスト実行 → Dockerイメージビルド → レジストリにプッシュ → SSHでサーバーにデプロイ → ヘルスチェック。テスト失敗とヘルスチェック失敗のブロックロジックを含めてください。

  3. チャレンジ (⭐⭐⭐):完全なBlue-Greenゼロダウンタイムデプロイソリューションを実装してください:2セットのDocker Compose設定, アクティブスロットの自動検出, 非アクティブスロットへのデプロイ, ヘルスチェック, Nginxアップストリーム切り替え, 古いスロットのグレースフルシャットダウン。30秒以内に前バージョンに戻せるロールバックスクリプトを記述してください。

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%