XServer VPSでDocker Compose×TypeScript本番環境を最小構成で構築する手順
この記事でわかること
XServer VPS(Ubuntu 22.04)に Docker Compose + TypeScript製アプリ + nginx(リバースプロキシ)+ Let’s Encrypt SSL + GitHub Actions自動デプロイ を揃えるまでの最小構成手順をまとめます。
「VPSを借りたけど本番運用まで持っていけない」という個人開発者がつまずくポイントを中心に、実際に筆者が使っている構成を公開します。
全体構成の概要
Internet
└─ nginx(443/80)
└─ TypeScriptアプリコンテナ(3000番)
└─ PostgreSQL コンテナ(任意)
| レイヤー | 役割 | 使うもの |
|---|---|---|
| VPS | インフラ | XServer VPS |
| コンテナ管理 | 起動・ネットワーク | Docker Compose v2 |
| アプリ | HTTP API / SSR | Node.js + TypeScript |
| リバースプロキシ | HTTPS終端・静的配信 | nginx |
| SSL | 証明書取得・更新 | Certbot(Let’s Encrypt) |
| CI/CD | 自動デプロイ | GitHub Actions + SSH |
ステップ1:XServer VPSの初期設定
VPSを契約したら、まずサーバーを最低限セキュアにします。
# rootでSSHログイン後
apt update && apt upgrade -y
# 作業ユーザー作成
adduser deploy
usermod -aG sudo deploy
# SSH公開鍵を設定
mkdir -p /home/deploy/.ssh
cat >> /home/deploy/.ssh/authorized_keys << 'EOF'
ssh-ed25519 AAAA... (your public key)
EOF
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh && chmod 600 /home/deploy/.ssh/authorized_keys
# rootログイン禁止・パスワード認証禁止
sed -i 's/^PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
sed -i 's/^PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
systemctl restart sshd
# ファイアウォール
ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable
ステップ2:DockerとDocker Compose v2 のインストール
# deployユーザーにスイッチ
su - deploy
# 公式スクリプトで Docker Engine インストール
curl -fsSL https://get.docker.com | sudo sh
# sudo なしで docker を使えるようにする
sudo usermod -aG docker deploy
newgrp docker
# バージョン確認(Compose は Docker CLI プラグインとして同梱)
docker --version # Docker 25.x 以上推奨
docker compose version # v2.x 以上推奨
注意:
docker-compose(v1)ではなくdocker compose(v2 プラグイン)を使います。XServer VPS の Ubuntu 22.04 イメージなら上記手順で v2 が入ります。
ステップ3:TypeScriptアプリのDockerfile
プロジェクト構成例:
my-app/
├── src/
│ └── index.ts
├── Dockerfile
├── docker-compose.yml
├── nginx/
│ └── default.conf
└── package.json
Dockerfile(マルチステージビルド)
# ---- build stage ----
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build # tsc → dist/
# ---- production stage ----
FROM node:20-alpine AS runner
ENV NODE_ENV=production
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/index.js"]
マルチステージにすることでイメージサイズを劇的に削減できます(開発依存が不要になるため)。
ステップ4:docker-compose.yml
version: "3.9"
services:
app:
build: .
restart: unless-stopped
environment:
- NODE_ENV=production
- DATABASE_URL=${DATABASE_URL}
networks:
- internal
nginx:
image: nginx:1.25-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
- /etc/letsencrypt:/etc/letsencrypt:ro
- certbot-webroot:/var/www/certbot
depends_on:
- app
networks:
- internal
volumes:
certbot-webroot:
networks:
internal:
nginx/default.conf
server {
listen 80;
server_name example.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location / {
proxy_pass http://app:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
ステップ5:Let’s Encrypt SSL証明書の取得
nginx を一度 HTTP のみで起動し、certbot でチャレンジを通します。
# certbot のインストール
sudo apt install -y certbot
# ドメインのAレコードがVPSのIPを向いていることを確認してから実行
sudo certbot certonly \
--webroot \
-w /var/lib/docker/volumes/my-app_certbot-webroot/_data \
-d example.com \
--email [email protected] \
--agree-tos \
--non-interactive
証明書は /etc/letsencrypt/live/example.com/ に生成されます。自動更新は certbot renew を cron または systemd タイマーで定期実行するだけです。
# 自動更新(週1回)
echo "0 3 * * 1 root certbot renew --quiet && docker compose -f /home/deploy/my-app/docker-compose.yml restart nginx" \
| sudo tee /etc/cron.d/certbot-renew
ステップ6:GitHub Actionsで自動デプロイ
.github/workflows/deploy.yml を追加します。
name: Deploy to XServer VPS
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy via SSH
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.VPS_HOST }}
username: deploy
key: ${{ secrets.VPS_SSH_KEY }}
script: |
cd ~/my-app
git pull origin main
docker compose build --no-cache
docker compose up -d --remove-orphans
docker image prune -f
GitHub リポジトリの Settings → Secrets and variables → Actions に以下を登録します。
| Secret名 | 値 |
|---|---|
VPS_HOST | VPSのIPアドレスまたはドメイン |
VPS_SSH_KEY | deployユーザーのSSH秘密鍵(ed25519推奨) |
main ブランチへの push で自動的にリビルド・再起動されます。
よくあるトラブルと対処法
permission denied でDockerが動かない
# deployユーザーをdockerグループに追加後、セッションを一度ログアウト・再ログインする
exit
ssh deploy@your-vps
nginx が443で起動しない
certbot 実行前は SSL ブロックをコメントアウトして HTTP のみで起動し、証明書取得後にコメントを外して再起動するのが定番の回避策です。
TypeScriptのビルドエラーが本番でだけ出る
tsconfig.json の strict: true は開発時から有効にして、ローカルでビルドを通してからプッシュする習慣をつけると防げます。
まとめ
XServer VPS + Docker Compose + TypeScript の本番環境最小構成をまとめると:
- VPS初期設定:SSHキー認証・UFW
- Docker CE + Compose v2 インストール
- マルチステージ Dockerfile でイメージを軽量化
- docker-compose.yml でアプリ・nginx を束ねる
- Certbot で Let’s Encrypt SSL を取得・自動更新
- GitHub Actions でゼロダウンタイムに近い自動デプロイ
個人開発の規模なら、この構成で月額数百円台のVPSで十分に本番運用できます。スケールが必要になったらコンテナをそのまま別の基盤に移行できるのもDocker活用の強みです。
Docker・インフラ周りをさらに体系的に学びたい方には、実践的な解説で定評のある以下の書籍もおすすめです。