Sausage Erectos X デプロイスクリプトの進化——手動scpから自動化まで

デプロイスクリプトの進化史——scpからPM2まで

個人開発のデプロイは、大規模チーム開発のそれとはまったく異なる。CI/CDパイプラインもKubernetesもない。あるのは1台のVPSと、ターミナルと、度胸だけだ。Sausage Erectos のデプロイ環境がどう進化してきたかを、Day 1 から振り返る。

Day 1: 手動scpの恐怖

プロジェクト初日のデプロイは、原始的なscpコマンドだった。

# Day 1 のデプロイ手順(恐怖)
npm run build
scp -r dist/* user@server:/var/www/sausage-erectos/
ssh user@server "sudo systemctl restart nginx"

この手順の問題は明らかだ。ビルド済みファイルを直接本番にコピーするため、ロールバック手段がない。何か問題があれば、前のバージョンのファイルは上書きされて消えている。

さらに恐ろしいのは、疲れた深夜のデプロイでタイプミスをするリスクだ。scpの送信先を間違えたら? 存在しないディレクトリに送ったら? その場で気づけるとは限らない。

Day 3: deploy.sh の誕生

3日目にして、手動デプロイの限界を悟り、最初のデプロイスクリプトを書いた。

#!/bin/bash
# deploy.sh v1.0 - Day 3

set -euo pipefail

REMOTE="user@server"
DEPLOY_DIR="/var/www/sausage-erectos"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/var/www/backups/$TIMESTAMP"

echo "=== Building... ==="
npm run build

echo "=== Creating archive... ==="
tar -czf dist.tar.gz -C dist .

echo "=== Backing up current version... ==="
ssh $REMOTE "mkdir -p $BACKUP_DIR && cp -r $DEPLOY_DIR/* $BACKUP_DIR/"

echo "=== Uploading... ==="
scp dist.tar.gz $REMOTE:/tmp/

echo "=== Extracting on server... ==="
ssh $REMOTE "cd $DEPLOY_DIR && tar -xzf /tmp/dist.tar.gz && rm /tmp/dist.tar.gz"

echo "=== Restarting Nginx... ==="
ssh $REMOTE "sudo systemctl restart nginx"

echo "=== Done! Deployed at $TIMESTAMP ==="
rm dist.tar.gz

set -euo pipefail が最初に学んだ教訓だ。エラーが起きたら即座に停止する。これがなければ、ビルドが失敗したのに古いファイルをデプロイしてしまう惨事が起きていたかもしれない。

バックアップ機能を追加したことで、ロールバックが可能になった。ssh $REMOTE "cp -r $BACKUP_DIR/* $DEPLOY_DIR/" の一行でロールバックできる安心感は大きい。

Day 5: PM2 との統合

Node.jsのバックエンドAPIを追加したDay 5で、Nginxだけでなくアプリケーションプロセスの管理も必要になった。PM2の導入だ。

# deploy.sh v2.0 - Day 5 (PM2統合)

# ... 前半は同じ ...

echo "=== Restarting application... ==="
ssh $REMOTE "cd $DEPLOY_DIR && pm2 restart ecosystem.config.js --env production"

echo "=== Health check... ==="
sleep 3
STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://sausage-erectos.com/api/health)
if [ "$STATUS" != "200" ]; then
  echo "!!! Health check failed! Rolling back... !!!"
  ssh $REMOTE "cp -r $BACKUP_DIR/* $DEPLOY_DIR/ && pm2 restart ecosystem.config.js"
  exit 1
fi
echo "=== Health check passed ($STATUS) ==="

ヘルスチェックの追加が重要なポイントだ。デプロイ後にAPIが正常に応答するかを自動確認し、失敗したら自動でロールバックする。

deploy.ps1(PowerShell版)の追加

開発環境がWindowsの場合もあるため、PowerShell版も作成した。

# deploy.ps1 - PowerShell version
$ErrorActionPreference = "Stop"
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"

Write-Host "=== Building... ===" -ForegroundColor Cyan
npm run build
if ($LASTEXITCODE -ne 0) { throw "Build failed" }

Write-Host "=== Deploying... ===" -ForegroundColor Cyan
tar -czf dist.tar.gz -C dist .
scp dist.tar.gz user@server:/tmp/
ssh user@server "cd /var/www/sausage-erectos && tar -xzf /tmp/dist.tar.gz"

Write-Host "=== Done! ===" -ForegroundColor Green

本番で rm -rf しかけた事故

ある夜、デプロイスクリプトを修正していて、変数展開のミスでこのコマンドを書いてしまった。

# 意図: rm -rf /var/www/sausage-erectos/dist
# 実際: rm -rf /var/www/sausage-erectos /dist
#                                      ↑ スペースが入っている!

幸い、set -euo pipefail のおかげで未定義変数の展開がエラーになり、実行前に停止した。もしこの安全装置がなかったら、本番サーバーの/var/www/sausage-erectosディレクトリ全体と、ルートの/dist(存在しないが)が削除されていた。

この事故未遂から、デプロイスクリプトには必ず「ドライラン」モードを追加するようにした。

CI/CDなしでも回る個人開発のデプロイ哲学

GitHub ActionsやCircleCIのようなCI/CDを導入しないのは、怠惰ではなく哲学だ。個人開発では「デプロイの過程を理解し、制御すること」自体に価値がある。

個人開発のデプロイ哲学:
  1. 自分が理解していないツールは使わない
  2. デプロイは自分の手で実行する(責任の所在を明確に)
  3. ロールバックは常に1コマンドで可能にする
  4. 本番環境の変更は必ずログを残す
  5. 深夜3時にはデプロイしない

デプロイスクリプトの進化は、失敗の歴史でもある。手動scpの恐怖を知っているからこそ、シェルスクリプトの一行一行に意味が宿る。CI/CDのない世界は不便だが、その不便さが「本当に必要なもの」を教えてくれる。

← ブログ一覧に戻る