💡 Tips

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 / SSRNode.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_HOSTVPSのIPアドレスまたはドメイン
VPS_SSH_KEYdeployユーザーのSSH秘密鍵(ed25519推奨)

main ブランチへの push で自動的にリビルド・再起動されます。


よくあるトラブルと対処法

permission denied でDockerが動かない

# deployユーザーをdockerグループに追加後、セッションを一度ログアウト・再ログインする
exit
ssh deploy@your-vps

nginx が443で起動しない

certbot 実行前は SSL ブロックをコメントアウトして HTTP のみで起動し、証明書取得後にコメントを外して再起動するのが定番の回避策です。

TypeScriptのビルドエラーが本番でだけ出る

tsconfig.jsonstrict: true は開発時から有効にして、ローカルでビルドを通してからプッシュする習慣をつけると防げます。


まとめ

XServer VPS + Docker Compose + TypeScript の本番環境最小構成をまとめると:

  1. VPS初期設定:SSHキー認証・UFW
  2. Docker CE + Compose v2 インストール
  3. マルチステージ Dockerfile でイメージを軽量化
  4. docker-compose.yml でアプリ・nginx を束ねる
  5. Certbot で Let’s Encrypt SSL を取得・自動更新
  6. GitHub Actions でゼロダウンタイムに近い自動デプロイ

個人開発の規模なら、この構成で月額数百円台のVPSで十分に本番運用できます。スケールが必要になったらコンテナをそのまま別の基盤に移行できるのもDocker活用の強みです。

Docker・インフラ周りをさらに体系的に学びたい方には、実践的な解説で定評のある以下の書籍もおすすめです。

📚 おすすめ書籍

Docker&仮想化コンテナ技術の教科書

インフラ初学者がコンテナ本番運用の全体像を掴むのに最適

Amazonで見る →