深夜2時、コーヒー4杯目のカフェインで心臓がバクバクしてる。本番環境のデプロイボタンを押す瞬間のあの独特の緊張感、あれって何年経っても慣れないよな。
今日は、手動デプロイという名の「修行」から卒業し、DockerとGitHub Actionsで泥臭くも堅牢なパイプラインを組んだ話を共有する。「自動化は魔法じゃない、ただの積み重ねだ」ってことを、俺の失敗と共に持ち帰ってくれ。
1. マルチステージビルド:イメージの「贅肉」を削ぎ落とす
適当に書いたDockerfileで1GB超えのイメージをECRにプッシュする……あの待ち時間、人生の無駄遣いにもほどがある。特に深夜の回線でアレをやると心が折れる。
解決策はマルチステージビルド一択だ。「ビルド環境」と「実行環境」を物理的に分離する。
# Build Stage
FROM node:18-slim AS builder
WORKDIR /app
COPY package*.json package-lock.json ./
RUN npm ci # npm installより高速で再現性が高い
COPY . .
RUN npm run build && npm prune --production # 開発用依存関係を容赦なく切り捨てる
# Production Stage
FROM node:18-slim
WORKDIR /app
# ビルド済みの成果物と、必要最小限のnode_modulesだけをコピー
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/main.js"]
ポイントは `npm ci` と `npm prune --production` だ。これだけでイメージサイズは800MBから150MBまで落ちる。「動けばいい」ではなく「最小限で動かす」。これがコンテナ時代のマナーだ。
2. 環境変数の管理:沼にはまるな
`.env`をGitに乗せてGitHubに公開し、秒速でマイニングサーバーにされた苦い思い出がある。あの時の絶望感と言ったら……。
Docker Composeで環境変数を扱うなら、潔く `.env` を活用しろ。`docker-compose.yml` と同じ階層に `.env` を置けば、Composeが勝手に読み込んでくれる。
# docker-compose.yml
services:
app:
image: my-app:${TAG:-latest} # デフォルト値の設定
env_file: .env
environment:
- NODE_ENV=production
教訓: 複雑なSecrets管理に手を出してSwarm専用の記法で詰まるより、環境ごとに `.env.prod` や `.env.dev` を用意し、起動時に `docker-compose --env-file .env.prod up -d` と叩く方が圧倒的に平和だ。「シンプルが最強」。これに尽きる。
3. GitHub Actions:深夜のデプロイを「無」にする
サーバーにSSHして `git pull` してビルド……そんな作業はもう古い。GitHub Actionsで、マージした瞬間にデプロイが完了するフローを作る。
重要なのは、サーバー上でビルドしないこと。サーバーに負荷をかけるな。GitHub上でビルドし、Docker HubにPushし、サーバーはそれをPullするだけにする。
# .github/workflows/deploy.yml
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build and Push
run: |
docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PASS }}
docker build -t my-app:latest .
docker push my-app:latest
deploy:
needs: build-and-push
runs-on: ubuntu-latest
steps:
- uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
script: |
docker pull my-app:latest
docker compose up -d --force-recreate # ダウンタイムを最小化
これで、コードをPushして寝れば、朝には最新版が動いている。最高だろ?
4. キャッシュ地獄からの脱出
今回一番ハマったのが「ビルドキャッシュ」だ。GitHub Actionsで `cache-from` を適当に使ったら、古いコードがコンテナに残る怪奇現象が起きた。
解決策: Actionsのキャッシュを正しく制御しろ。
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Build and push
uses: docker/build-push-action@v4
with:
push: true
tags: my-app:latest
cache-from: type=gha
cache-to: type=gha,mode=max
`mode=max` を指定しないと、中間レイヤーのキャッシュが不完全で、結局ビルドが遅くなる。便利機能に頼る時は、その裏側の仕様まで理解しておかないと、深夜3時に顧客からの「画面が変わってないぞ」というチャットで死ぬことになる。
結論:泥臭く、しかしスマートに
自動化は正義だけど、構築過程で一度は死ぬ。でも、その死を乗り越えた先にある「寝てる間にデプロイが終わってる」快感は、エンジニアにしか分からない麻薬だ。
結局、一番のデバッグツールは「深夜の集中力」と「諦めない心」だ。……さて、コーヒー、もう一杯入れてくるわ。
自動化で楽をするために、今日も泥臭くコンテナを叩く。