server-deploy について
server-deployは、ローカルのプロジェクトをリモートのLinuxサーバーへ配置するデプロイスキルです。ファイルからプロジェクト種別を判別し、環境構築、ビルド、プロセス管理、Webサーバー設定の手順を選びます。
主な機能
- 01Node.js、Python、Rust、Go、Docker、静的サイトの判別
- 02リモートサーバーの環境とリソースの確認
- 03SSHとrsyncによるプロジェクト転送
- 04種別に応じた依存関係の導入、ビルド、起動
- 05Nginxのリバースプロキシまたは静的ホスティング設定
- 06ドメインとSSLの設定手順
活用シーン
- 01ローカルアプリのリモートLinuxサーバーへのデプロイ
- 02種別に応じたビルドとプロセス管理方法の選択
- 03デプロイ先でのドメイン、Nginx、SSLの設定
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ majiayu000/ spellbook --skill server-deployインストール後、Claude Code で/server-deployを入力して読み込まれたか確認します。
同梱ファイルreference/extended.md
詳細
汎用サーバーのデプロイ
ローカルプロジェクトをリモート Linux サーバーにワンクリックでデプロイします。プロジェクトの種類を自動識別し、適切なビルド、実行、プロセス管理の方法を適用します。
第零ステップ:プロジェクトの識別と情報収集
0a. プロジェクトの種類を自動識別
プロジェクトのルートディレクトリをスキャンし、以下のルールに従って種類を判定します。
| 目印となるファイル | プロジェクトの種類 | ランタイム | プロセス管理 |
|---|---|---|---|
package.json | Node.js | node | PM2 |
Cargo.toml | Rust | コンパイル済み成果物 | systemd |
go.mod | Go | コンパイル済み成果物 | systemd |
pyproject.toml / requirements.txt / setup.py | Python | python3 | systemd / gunicorn |
Dockerfile / docker-compose.yml | Docker | docker | docker compose |
index.html(ほかに目印となるファイルがない場合) | 静的サイト | なし | Nginx で直接ホスティング |
複数の目印となるファイル(例:package.json と Dockerfile)が検出された場合は、AskUserQuestion を使ってユーザーにデプロイ方法を選択してもらいます。
0b. 情報を収集
AskUserQuestion を使って以下の情報を収集します(コンテキストにすでにある情報は省略します)。
-
SSH 接続(header: "サーバー")
- コンテキストに基づいて選択肢を動的に生成します(例:以前に使用したサーバー IP)
- 予備の選択肢として「その他のサーバー」
-
プロジェクトのパス(header: "プロジェクト")
- 現在の作業ディレクトリから自動的に推定します
- 不明な場合はユーザーに尋ねます
-
ドメイン(header: "ドメイン")
- ドメインがある場合(ユーザーに入力してもらいます)
- IP のみでアクセスする場合(ドメインと SSL の設定を省略します)
-
実行ポート(header: "ポート")
- 自動検出:
- Node.js:package.json の scripts または .env から抽出
- Python:main.py / app.py / manage.py から抽出
- Rust/Go:main.rs / main.go または設定ファイルから抽出
- 検出できない場合は、ユーザーに指定してもらいます
- 3000, 3001, 4000, 5000, 5173, 8000, 8080, 8888 の使用は禁止です
- 自動検出:
0c. 変数の定義
| 変数 | 説明 |
|---|---|
$SSH_TARGET | SSH 接続文字列(例:[email protected]) |
$PROJECT_DIR | ローカルプロジェクトの絶対パス |
$PROJECT_NAME | プロジェクト名 |
$PROJECT_TYPE | プロジェクトの種類:nodejs / python / rust / go / docker / static |
$REMOTE_DIR | リモートのデプロイ先ディレクトリ(デフォルトは /opt/$PROJECT_NAME) |
$PORT | アプリケーションの実行ポート |
$DOMAIN | ドメイン(任意) |
第一步:SSH 接続と鍵の設定
1a. ローカルの SSH 公開鍵を確認
cat ~/.ssh/id_rsa.pub 2>/dev/null || cat ~/.ssh/id_ed25519.pub 2>/dev/null
公開鍵がない場合は、次のコマンドで生成します:ssh-keygen -t ed25519 -N "" -f ~/.ssh/id_ed25519
1b. パスワードなしでログインできるかテスト
ssh -o ConnectTimeout=5 -o BatchMode=yes $SSH_TARGET "echo ok" 2>/dev/null
失敗した場合は、パスワードでログインして公開鍵を追加します。
sshpass -p '<PASSWORD>' ssh -o StrictHostKeyChecking=no $SSH_TARGET \
"mkdir -p ~/.ssh && chmod 700 ~/.ssh && echo '<PUBLIC_KEY>' >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
1c. パスワードなしでログインできることを確認
ssh -o ConnectTimeout=5 $SSH_TARGET "echo '免密登录成功' && uname -a"
第二步:サーバー環境の確認
1つのコマンドですべての情報を取得します:
ssh $SSH_TARGET "echo '=== 系统 ===' && cat /etc/os-release | grep PRETTY_NAME && uname -m && echo '=== CPU ===' && nproc && echo '=== 内存 ===' && free -h && echo '=== 磁盘 ===' && df -h / && echo '=== 包管理器 ===' && which apt yum dnf pacman 2>/dev/null && echo '=== Node.js ===' && node -v 2>/dev/null || echo '未安装' && echo '=== Python ===' && python3 --version 2>/dev/null || echo '未安装' && echo '=== Rust ===' && rustc --version 2>/dev/null || echo '未安装' && echo '=== Go ===' && go version 2>/dev/null || echo '未安装' && echo '=== Docker ===' && docker --version 2>/dev/null || echo '未安装' && echo '=== Nginx ===' && nginx -v 2>&1 || echo '未安装'"
確認ポイント:
- ディスク容量が十分か
- メモリ:Node.js は 512MB 以上、Rust のコンパイルは 1GB 以上(またはローカルでクロスコンパイル)
- アーキテクチャ:
x86_64/aarch64(Rust/Go はコンパイルターゲットを一致させる必要があります) - 不足しているコンポーネントを記録し、後でまとめてインストールします
第三步:ランタイム環境のインストール
$PROJECT_TYPE に応じて、該当するランタイムをインストールします。不足しているコンポーネントのみをインストールしてください。
Node.js プロジェクト
# 安装 Node.js 20(如未安装)
ssh $SSH_TARGET "curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && apt-get install -y nodejs"
# 安装 PM2
ssh $SSH_TARGET "npm install -g pm2"
Python プロジェクト
# 安装 Python3 + pip + venv(如未安装)
ssh $SSH_TARGET "apt-get install -y python3 python3-pip python3-venv"
Rust プロジェクト
サーバーのリソースに応じて、次の 2 つの方法から選択します。
方法 A:サーバー上でコンパイル(メモリ 1GB 以上)
ssh $SSH_TARGET "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && source ~/.cargo/env"
方法 B:ローカルでクロスコンパイル(低スペックサーバーに推奨)
# 本地编译(需安装对应 target)
rustup target add x86_64-unknown-linux-gnu # 或 aarch64-unknown-linux-gnu
cargo build --release --target x86_64-unknown-linux-gnu
# 仅上传编译产物
Go プロジェクト
同様に、次の 2 つの方法があります。
方法 A:サーバー上でコンパイル
ssh $SSH_TARGET "wget -q https://go.dev/dl/go1.22.0.linux-amd64.tar.gz && tar -C /usr/local -xzf go1.22.0.linux-amd64.tar.gz && echo 'export PATH=\$PATH:/usr/local/go/bin' >> ~/.bashrc"
方法 B:ローカルでクロスコンパイル(推奨)
GOOS=linux GOARCH=amd64 go build -o $PROJECT_NAME .
# 仅上传二进制文件
Docker プロジェクト
# 安装 Docker(如未安装)
ssh $SSH_TARGET "curl -fsSL https://get.docker.com | sh && systemctl enable docker && systemctl start docker"
Nginx(ドメインがある場合は必須)
ssh $SSH_TARGET "apt-get install -y nginx && systemctl enable nginx && systemctl start nginx"
第四步:プロジェクトをアップロード
4a. 除外するもの(プロジェクトの種類別)
| プロジェクトの種類 | 除外するディレクトリ/ファイル |
|---|---|
| Node.js | node_modules/, .next/, dist/, .git/ |
| Python | __pycache__/, .venv/, venv/, *.pyc, .git/ |
| Rust | target/, .git/(方法 B では target/release/$BINARY のみをアップロード) |
| Go | vendor/(ある場合), .git/(方法 B ではバイナリのみをアップロード) |
| Docker | .git/(Dockerfile のアップロードが必要です) |
| 静的サイト | .git/, node_modules/ |
4b. rsync でアップロード
注意:大きなファイルを転送する際に SSH 接続が切断されることがあります。接続維持用のパラメーターを使用してください。
rsync -avz --timeout=300 \
-e "ssh -o ServerAliveInterval=30 -o ServerAliveCountMax=10" \
--exclude='.git' --exclude='<TYPE_SPECIFIC_EXCLUDES>' \
$PROJECT_DIR/ $SSH_TARGET:$REMOTE_DIR/
ファイルが非常に大きい場合(>500MB):まずコードをアップロードし、大きなファイルは個別にアップロードします。
SSH 接続が頻繁に切断される場合:サーバーの SSH 設定を調整します。
ssh $SSH_TARGET "sed -i 's/#ClientAliveInterval.*/ClientAliveInterval 60/' /etc/ssh/sshd_config && sed -i 's/#ClientAliveCountMax.*/ClientAliveCountMax 120/' /etc/ssh/sshd_config && systemctl reload ssh || systemctl reload sshd"
4c. 依存関係のインストールとビルド
接続が切断されても中断されないよう、nohup を使ってバックグラウンドで実行します。
| プロジェクトの種類 | コマンド |
|---|---|
| Node.js | nohup bash -c 'npm install && npm run build' > /tmp/build.log 2>&1 & |
| Python | nohup bash -c 'python3 -m venv .venv && .venv/bin/pip install -r requirements.txt' > /tmp/build.log 2>&1 & |
| Rust(方法 A) | nohup bash -c 'source ~/.cargo/env && cargo build --release' > /tmp/build.log 2>&1 & |
| Go(方法 A) | nohup bash -c 'go build -o $PROJECT_NAME .' > /tmp/build.log 2>&1 & |
| Docker | nohup bash -c 'docker compose build' > /tmp/build.log 2>&1 & |
| 静的サイト | ビルド不要 |
完了するまで待ち、ビルド結果を確認します:ssh $SSH_TARGET "tail -20 /tmp/build.log"
第五步:アプリケーションの起動とプロセス管理
プロジェクトの種類に応じて、適切なプロセス管理方法を選択します。
Node.js → PM2
ssh $SSH_TARGET "cd $REMOTE_DIR && pm2 start npm --name $PROJECT_NAME -- start && pm2 startup && pm2 save"
確認:ssh $SSH_TARGET "pm2 list && curl -s -o /dev/null -w '%{http_code}' http://localhost:$PORT"
Python → systemd
ssh $SSH_TARGET "cat > /etc/systemd/system/$PROJECT_NAME.service << EOF
[Unit]
Description=$PROJECT_NAME
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=$REMOTE_DIR
ExecStart=$REMOTE_DIR/.venv/bin/python -m uvicorn main:app --host 0.0.0.0 --port $PORT
# 或 gunicorn: ExecStart=$REMOTE_DIR/.venv/bin/gunicorn -w 4 -b 0.0.0.0:$PORT main:app
Restart=on-failure
RestartSec=5
Environment=PYTHONUNBUFFERED=1
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload && systemctl enable $PROJECT_NAME && systemctl start $PROJECT_NAME"
注意:ExecStart はフレームワークに応じて調整してください。
- FastAPI/Starlette:
uvicorn main:app- Flask:
gunicorn -w 4 main:app- Django:
gunicorn -w 4 project.wsgi:application- 通常のスクリプト:
python main.py
確認:ssh $SSH_TARGET "systemctl status $PROJECT_NAME && curl -s -o /dev/null -w '%{http_code}' http://localhost:$PORT"
Rust / Go → systemd
ssh $SSH_TARGET "cat > /etc/systemd/system/$PROJECT_NAME.service << EOF
[Unit]
Description=$PROJECT_NAME
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=$REMOTE_DIR
ExecStart=$REMOTE_DIR/$BINARY_NAME
Restart=on-failure
RestartSec=5
Environment=PORT=$PORT
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload && systemctl enable $PROJECT_NAME && systemctl start $PROJECT_NAME"
Rust バイナリのパス:
$REMOTE_DIR/target/release/$PROJECT_NAME(方式 A)または$REMOTE_DIR/$PROJECT_NAME(方式 B) Go バイナリのパス:$REMOTE_DIR/$PROJECT_NAME
確認:ssh $SSH_TARGET "systemctl status $PROJECT_NAME && curl -s -o /dev/null -w '%{http_code}' http://localhost:$PORT"
Docker → docker compose
ssh $SSH_TARGET "cd $REMOTE_DIR && docker compose up -d"
起動時の自動起動を設定:Docker サービスはデフォルトで systemd とともに起動するため、docker compose では restart: unless-stopped を設定するだけで十分です。
確認:ssh $SSH_TARGET "docker compose ps && curl -s -o /dev/null -w '%{http_code}' http://localhost:$PORT"
静的サイト → Nginx で直接ホスティング
プロセス管理は不要です。Nginx から静的ファイルのディレクトリを直接参照します(第六步を参照)。
第六步:Nginx リバースプロキシ
ユーザーが「IP のみでアクセス」を選択した場合、この手順と以降の SSL 手順はスキップします。
6a. 動的アプリ(HTTP リバースプロキシ)
ssh $SSH_TARGET "cat > /etc/nginx/sites-available/$PROJECT_NAME << 'NGINX'
server {
listen 80;
server_name $DOMAIN www.$DOMAIN;
client_max_body_size 50M;
location / {
proxy_pass http://127.0.0.1:$PORT;
proxy_http_version 1.1;
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;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection \"upgrade\";
}
}
NGINX
ln -sf /etc/nginx/sites-available/$PROJECT_NAME /etc/nginx/sites-enabled/$PROJECT_NAME
rm -f /etc/nginx/sites-enabled/default
nginx -t && systemctl reload nginx"
6b. 静的サイト(直接ホスティング)
ssh $SSH_TARGET "cat > /etc/nginx/sites-available/$PROJECT_NAME << 'NGINX'
server {
listen 80;
server_name $DOMAIN www.$DOMAIN;
root $REMOTE_DIR;
index index.html;
location / {
try_files \$uri \$uri/ /index.html;
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 30d;
add_header Cache-Control \"public, immutable\";
}
}
NGINX
ln -sf /etc/nginx/sites-available/$PROJECT_NAME /etc/nginx/sites-enabled/$PROJECT_NAME
rm -f /etc/nginx/sites-enabled/default
nginx -t && systemctl reload nginx"
6c. HTTPS バージョン(SSL 証明書がある場合)
該当する server ブロックをベースにして、次のようにします。
- ポート 80 の server に
return 301 https://$host$request_uri;を追加します。 - ポート 443 の server ブロックを新規追加し、ssl_certificate とセキュリティヘッダーを設定します。
server {
listen 443 ssl;
server_name $DOMAIN www.$DOMAIN;
ssl_certificate /etc/ssl/cloudflare/fullchain.pem;
ssl_certificate_key /etc/ssl/cloudflare/key.pem;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# ... location 块同上 ...
}
6d. Nginx のセキュリティ強化
ssh $SSH_TARGET "sed -i 's/# server_tokens off;/server_tokens off;/' /etc/nginx/nginx.conf && nginx -t && systemctl reload nginx"
第七步:DNS と SSL の設定
ユーザーがドメインを所有している場合にのみ実行します。
7a. DNS 設定の案内
ユーザーに、ドメイン登録事業者の管理画面で DNS レコードを追加するよう案内します。
| レコードタイプ | 名前 | 内容 |
|---|---|---|
| A | @ | <サーバーIP> |
| A | www | <サーバーIP> |
Cloudflare を使用する場合は、オレンジ色の雲(プロキシモード)を有効にすることをおすすめします。
7b. SSL 方式の選択
AskUserQuestion を使って、次の選択肢を提示します。
- Cloudflare Origin Certificate(推奨) — DNS が Cloudflare でホスティングされている場合に利用でき、有効期間は 15 年です。
- Let's Encrypt — 無料のパブリック証明書です。定期的な更新が必要です。
- SSL を設定しない
Cloudflare Origin Certificate の手順:
-
ユーザーに次の操作を案内します:Cloudflare → SSL/TLS → オリジンサーバー → 証明書を作成
- ホスト名に
$DOMAINと*.$DOMAINが含まれていることを確認します。 - 秘密鍵の種類:RSA、有効期間:15 年
- ホスト名に
-
ユーザーから証明書と秘密鍵を受け取った後、サーバーに書き込み、証明書チェーンを連結します。
ssh $SSH_TARGET "mkdir -p /etc/ssl/cloudflare"
# 写入 cert.pem 和 key.pem(chmod 600)
# 下载 Origin CA 根证书并拼接
ssh $SSH_TARGET "curl -s -o /etc/ssl/cloudflare/origin-ca-rsa-root.pem https://developers.cloudflare.com/ssl/static/origin_ca_rsa_root.pem && cat /etc/ssl/cloudflare/cert.pem /etc/ssl/cloudflare/origin-ca-rsa-root.pem > /etc/ssl/cloudflare/fullchain.pem"
- 証明書を必ず検証します:
ssh $SSH_TARGET "openssl x509 -in /etc/ssl/cloudflare/cert.pem -noout -text | grep -E 'DNS:|Subject:|Issuer:|Extended'"
DNS:にドメインが含まれていることExtended Key UsageにTLS Web Server Authenticationが含まれていること- 含まれていない場合は、ユーザーに再作成するよう案内します。
- Cloudflare の SSL モードは、まず Full (Strict) に設定します。526 エラーが発生した場合は Full に変更します。
Let's Encrypt の手順:
ssh $SSH_TARGET "apt-get install -y certbot python3-certbot-nginx && certbot --nginx -d $DOMAIN -d www.$DOMAIN --non-interactive --agree-tos --email <EMAIL>"
Cloudflare のプロキシを一時的に無効(オレンジからグレー)にする必要があります。完了後は再度有効にできます。
拡張リファレンス
このスキルを簡潔に保つため、## 第八步:安全加固 以降の詳細資料は reference/extended.md に移動しました。移動したサンプル、コマンド一覧、チェックリスト、プラットフォームの詳細、実装テンプレートが必要な場合は、このリファレンスを読み込んでください。