0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AWS Lightsail + GitHub Actionsで低コストWebアプリインフラ構築

0
Last updated at Posted at 2026-01-31

1. はじめに

小規模案件やPoC案件では、インフラ費用をできるだけ抑えたい要望があります。しかし、EC2やECSはコストが高く、AWS以外のVPSも検討しました。

ただし、AWS以外のVPSサービスを利用すると、キャッチアップコストがかかることや拡張性に不安がありました。そこで、AWS LightsailとGitHub Actionsを組み合わせた構成を検討しました。

Lightsail構成はコスト・パフォーマンスともに十分です。基本構成で約48ドル、WAF追加で約55ドル、バックアップ付きでも約83ドルに収まります。

この記事では、構成案の概要と、LightsailでのDocker ComposeおよびGitHub Actionsによるデプロイフローを中心にまとめています。

2. 対象読者

  • Lightsailは未経験だが、AWSやCI/CDには少し触れたことがある
  • GitHub Actions で Docker をビルドしてデプロイしたい

3. 目次

4. 環境

  • AWS Lightsail: 4GB RAM / 2vCPU / 80GB SSD
  • OS: Ubuntu 22.04 LTS
  • コンテナ: Docker Compose
  • ルーティング: ALB + ACM
  • セキュリティ: WAF v2
  • DNS: Route 53
  • CI/CD: GitHub Actions + GHCR

5. 本編

5-1. 構成案

構成名 月額費用 内容 説明
構成a $24 Lightsail+CloudWatch 最小限インフラ、監視のみのシンプル構成
構成b $48 Lightsail+ALB+ACM+CloudWatch HTTPS対応、監視機能を含む基本構成
構成c $55 構成b+WAF セキュリティ対策を追加した推奨構成
構成d $83 構成c+Lightsailバックアップ バックアップ機能を含むフル構成

本記事では構成cを採用しています。

理由は、セキュリティ、コストバランス、拡張性の3点が、PoCや開発環境に最適だと判断したためです。構成bではWAFがなくセキュリティが不十分です。一方、構成dはバックアップコスト($28/月)が開発環境には過剰と考えました。

5-2. 全体構成

本記事で構築するWebアプリのインフラ構成は以下の通りです。

デプロイフロー :

  • GitHub Actionsがビルド時にSecrets Managerから環境変数を取得
  • ビルドしたコンテナイメージをGHCRにpush
  • LightsailがGHCRからコンテナイメージをpullしてデプロイ

ユーザーアクセスフロー :

  • インターネットからのアクセスをRoute 53が受け、ALBに転送
  • ALBにはWAFが紐づき、不正アクセス対策
  • ACMで証明書を管理し、HTTPS通信を実現

監視・運用:

  • Lightsail上のCloudWatch AgentがログをCloudWatch Logsに送信

5-3. ディレクトリ構成

今回はフロントエンドとバックエンドの2層構成を前提にしています。

project/
├── front/                      # フロントエンド
│   ├── Dockerfile              # ローカル開発用
│   ├── Dockerfile.dev          # GitHub Container Registry用(開発環境)
│   └── src/
├── backend/                    # バックエンド
│   ├── Dockerfile              # ローカル開発用
│   ├── Dockerfile.dev          # GitHub Container Registry用(開発環境)
│   └── src/
├── deploy/
│   └── development/            # Lightsail開発環境専用
│       ├── docker-compose.yml
│       ├── nginx/
│       │   └── nginx.conf
│       ├── cloudwatch-agent/   # CloudWatch Agent設定
│       │   └── config.json
│       ├── logrotate/          # ログローテーション設定
│       │   └── logrotate.conf
│       └── README.md
├── docker-compose.yml          # ローカル開発環境用
├── .github/
│   └── workflows/
│       └── deploy-dev.yml
└── README.md

deploy/development/ 配下を Lightsail 用の設定置き場にしています。
Dockerfilenginx.confconfig.jsonlogrotate.conf の詳細な説明は割愛します。

それでは、次にLightsailのセットアップを進めていきます。

5-4. Lightsailの準備

  • Lightsailインスタンス作成(Ubuntu 22.04)
  • 静的IPをアタッチ
  • ファイアウォール開放
    • 22 (SSH)
    • 80 (HTTP)
    • 443 (HTTPS)
    • 8080 (ALBヘルスチェック)
  • SSHキーを取得してローカルに保管

SSHキーは GitHub Actions からのデプロイにも使用します。

5-5. Lightsail初期セットアップ

Lightsailインスタンス作成後、SSH接続して初期セットアップを実行します。
ログファイルは各コンテナごとに区切られたディレクトリに保存します。

以下のスクリプトを setup.sh として保存し、Lightsail上で実行します。

#!/bin/bash

set -e
set -u

LOG_FILE="/tmp/docker-setup-$(date +%Y%m%d-%H%M%S).log"
exec 1> >(tee -a "$LOG_FILE")
exec 2> >(tee -a "$LOG_FILE" >&2)

sudo apt update
sudo apt install -y apt-transport-https ca-certificates curl software-properties-common

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io
sudo apt install -y docker-compose

mkdir -p /home/ubuntu/app
mkdir -p /home/ubuntu/app/logs/{front,backend,mysql,redis,nginx}

chown -R ubuntu:ubuntu /home/ubuntu/app/logs

sudo usermod -aG docker ubuntu

スクリプト実行後、再度SSHログインして、Dockerが使えることを確認します。

docker --version
docker compose version

5-6. Docker Compose設定

Lightsailインスタンス上で動作させる docker-compose.yml の設定です。
deploy/development/ ディレクトリに配置することを想定しています。

services:
  nginx:
    image: nginx:1.25-alpine
    container_name: dev-nginx
    ports:
      - "80:80"
      - "8080:8080"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - /home/ubuntu/app/logs/nginx:/var/log/nginx
      - backend-files:/var/www/html:ro
    depends_on:
      - front
      - backend
    restart: always
    networks:
      - app-network

  front:
    image: ${FRONT_IMAGE:-ghcr.io/mycompany/app/dev-app-front:latest}
    container_name: dev-front
    user: "1000:1000"
    env_file:
      - .env.front.development
    environment:
      - APP_NAME=front
    volumes:
      - /home/ubuntu/app/logs/front:/var/log/app/front
    restart: always
    networks:
      - app-network

  backend:
    image: ${BACKEND_IMAGE:-ghcr.io/mycompany/app/dev-app-backend:latest}
    container_name: dev-backend
    user: "33:33"
    env_file:
      - .env.backend.development
    environment:
      - APP_NAME=backend
    volumes:
      - backend-files:/var/www/html
      - /home/ubuntu/app/logs/backend:/var/log/app/backend
    depends_on:
      - db
      - redis
    restart: always
    networks:
      - app-network

  db:
    image: mysql:8.4
    container_name: dev-mysql
    environment:
      MYSQL_DATABASE: app_db
      MYSQL_USER: app_user
      MYSQL_PASSWORD: ${DB_PASSWORD}
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
    volumes:
      - mysql-data:/var/lib/mysql
      - /home/ubuntu/app/logs/mysql:/var/log/app/mysql
    command: >
      --mysql-native-password=ON
      --character-set-server=utf8mb4
      --collation-server=utf8mb4_unicode_ci
      --log-error=/var/log/app/mysql/mysql-error.log
    ports:
      - "127.0.0.1:3306:3306"
    restart: always
    networks:
      - app-network
    depends_on:
      - cloudwatch-agent

  redis:
    image: redis:7.2-alpine
    container_name: dev-redis
    command: redis-server --requirepass ${REDIS_PASSWORD}
    volumes:
      - redis-data:/data
      - /home/ubuntu/app/logs/redis:/var/log/app/redis
    ports:
      - "127.0.0.1:6379:6379"
    restart: always
    networks:
      - app-network

  cloudwatch-agent:
    image: amazon/cloudwatch-agent:latest
    container_name: dev-cloudwatch-agent
    env_file:
      - .env.cloudwatch.development
    volumes:
      - ./cloudwatch-agent/config.json:/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json:ro
      - ./cloudwatch-agent/config.json:/opt/aws/amazon-cloudwatch-agent/bin/default_linux_config.json:ro
      - ./cloudwatch-agent/common-config.toml:/opt/aws/amazon-cloudwatch-agent/etc/common-config.toml:ro
      - ./cloudwatch-agent/config.json:/etc/cwagentconfig/amazon-cloudwatch-agent.json:ro
      - /home/ubuntu/app/logs:/var/log/app:ro
    restart: always
    networks:
      - app-network
    command: ["/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl", "-a", "fetch-config", "-m", "onPremise", "-s", "-c", "file:/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json"]

volumes:
  mysql-data:
  redis-data:
  backend-files:

networks:
  app-network:
    driver: bridge

5-7. ALB / ACM / WAFのセットアップ概要

この記事ではDocker ComposeとCI/CDに焦点を当てているため、ALB・ACM・WAFの詳細な手順は省略します。

以下の流れで設定を進めてください。

  • ACMで証明書を発行(ワイルドカード + ルート)
  • ALBを作成し、HTTPSリスナーをACMに紐付け
  • ALBのターゲットにLightsailのプライベートIPを登録
  • WAFはALBに紐付け

ドメインの具体的な設定は省略しますが、Route 53でALB向けのエイリアスを作る前提です。

5-8. GitHub Actionsワークフロー

develop ブランチに push されたら自動デプロイ という想定です。

以下のワークフローを .github/workflows/deploy-dev.yml に配置します。

name: Deploy to Development Environment

on:
  push:
    branches: [develop]
    paths:
      - 'front/**'
      - 'backend/**'
      - '.github/workflows/develop-deploy.yml'

permissions:
  contents: read
  packages: write

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        app: [front, backend]
      fail-fast: true
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set lowercase repository name
        run: |
          echo "REPO_NAME=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v2
        with:
          aws-access-key-id: ${{ secrets.DEV_AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.DEV_AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-1

      - name: Generate environment file from Secrets Manager
        run: |
          generate_env_from_secret() {
            local secret_name=$1
            local output_file=$2
            
            secret_json=$(aws secretsmanager get-secret-value \
              --secret-id "$secret_name" \
              --query SecretString \
              --output text \
              --region ap-northeast-1)
            
            echo "$secret_json" | jq -r 'to_entries|map("\(.key)=\(.value|tostring)")|.[]' > "$output_file"
          }
          
          generate_env_from_secret "dev-app-${{ matrix.app }}" ".env.${{ matrix.app }}.build"

      - name: Copy environment file to build context
        run: |
          APP_NAME="${{ matrix.app }}"
          cp ".env.${APP_NAME}.build" "${APP_NAME}/.env.${APP_NAME}.build"

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push ${{ matrix.app }}
        uses: docker/build-push-action@v5
        with:
          context: .
          file: ./${{ matrix.app }}/Dockerfile.dev
          push: true
          build-args: |
            ${{ matrix.app != 'backend' && format('NPM_TOKEN={0}', secrets.GITHUB_TOKEN) || '' }}
          tags: |
            ghcr.io/${{ env.REPO_NAME }}/dev-app-${{ matrix.app }}:latest
            ghcr.io/${{ env.REPO_NAME }}/dev-app-${{ matrix.app }}:${{ github.sha }}

  deploy:
    needs: build-and-push
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set lowercase repository name
        run: |
          echo "REPO_NAME=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v2
        with:
          aws-access-key-id: ${{ secrets.DEV_AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.DEV_AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-1

      - name: Generate environment files from Secrets Manager
        run: |
          generate_env_from_secret() {
            local secret_name=$1
            local output_file=$2
            
            secret_json=$(aws secretsmanager get-secret-value \
              --secret-id "$secret_name" \
              --query SecretString \
              --output text \
              --region ap-northeast-1)
            
            echo "$secret_json" | jq -r 'to_entries|map("\(.key)=\(.value|tostring)")|.[]' > "$output_file"
          }
          
          generate_env_from_secret "dev-app-backend" ".env.backend.build"
          generate_env_from_secret "dev-app-front" ".env.front.build"

      - name: Prepare deployment environment files
        run: |
          cp .env.front.build .env.front.development
          cp .env.backend.build .env.backend.development
          
          DB_PASSWORD=$(grep '^DB_PASSWORD=' .env.backend.build | cut -d'=' -f2-)
          MYSQL_ROOT_PASSWORD=$(grep '^MYSQL_ROOT_PASSWORD=' .env.backend.build | cut -d'=' -f2-)
          REDIS_PASSWORD=$(grep '^REDIS_PASSWORD=' .env.backend.build | cut -d'=' -f2-)
          
          cat > .env.development << EOF
          DB_PASSWORD=${DB_PASSWORD}
          MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
          REDIS_PASSWORD=${REDIS_PASSWORD}
          EOF
          
          cat > .env.cloudwatch.development << EOF
          AWS_ACCESS_KEY_ID=${{ secrets.DEV_AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY=${{ secrets.DEV_AWS_SECRET_ACCESS_KEY }}
          AWS_DEFAULT_REGION=ap-northeast-1
          EOF

      - name: Deploy to Lightsail Development
        env:
          HOST: ${{ secrets.DEV_LIGHTSAIL_HOST }}
          USERNAME: ${{ secrets.DEV_LIGHTSAIL_USERNAME }}
          KEY: ${{ secrets.DEV_LIGHTSAIL_SSH_KEY }}
        run: |
          SSH_KEY_FILE=$(mktemp)
          trap 'rm -f "$SSH_KEY_FILE" .env.*.development .env.development' EXIT
          
          echo "$KEY" > "$SSH_KEY_FILE"
          chmod 600 "$SSH_KEY_FILE"
          
          SCP_OPTS="-o StrictHostKeyChecking=no -i $SSH_KEY_FILE"
          DEST="$USERNAME@$HOST:/home/ubuntu/app/development/"
          
          scp -r $SCP_OPTS deploy/development/ $USERNAME@$HOST:/home/ubuntu/app/
          scp $SCP_OPTS .env.front.development .env.backend.development .env.cloudwatch.development $DEST
          scp $SCP_OPTS .env.development $USERNAME@$HOST:/home/ubuntu/app/development/.env
          
          SSH_OPTS="-o StrictHostKeyChecking=no -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -i $SSH_KEY_FILE"
          
          ssh $SSH_OPTS $USERNAME@$HOST "
            cd /home/ubuntu/app/development
            
            echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
            
            docker pull ghcr.io/${{ env.REPO_NAME }}/dev-app-front:${{ github.sha }}
            docker pull ghcr.io/${{ env.REPO_NAME }}/dev-app-backend:${{ github.sha }}
            
            export FRONT_IMAGE=ghcr.io/${{ env.REPO_NAME }}/dev-app-front:${{ github.sha }}
            export BACKEND_IMAGE=ghcr.io/${{ env.REPO_NAME }}/dev-app-backend:${{ github.sha }}
            
            docker compose down
            docker volume rm development_backend-files || true
            docker compose up -d
            
            docker images ghcr.io/${{ env.REPO_NAME }}/dev-app-front --format \"table {{.Repository}}:{{.Tag}}\" | grep -v \"latest\" | tail -n +3 | xargs -r docker rmi || true
            docker images ghcr.io/${{ env.REPO_NAME }}/dev-app-backend --format \"table {{.Repository}}:{{.Tag}}\" | grep -v \"latest\" | tail -n +3 | xargs -r docker rmi || true
            
            docker system prune -f
            docker image prune -f
          "

GitHub Secrets の設定

GitHubリポジトリの Settings > Secrets and variables > Actions から以下を設定します。

Secret名 説明 備考
DEV_AWS_ACCESS_KEY_ID AWSアクセスキーID IAMユーザーを作成し、Secrets Manager・CloudWatch Logsの権限を付与
DEV_AWS_SECRET_ACCESS_KEY AWSシークレットアクセスキー IAMユーザー作成時に取得
DEV_LIGHTSAIL_HOST Lightsailの静的IP Lightsailコンソールから静的IPを作成・アタッチして取得
DEV_LIGHTSAIL_USERNAME SSH接続用ユーザー名
DEV_LIGHTSAIL_SSH_KEY SSH秘密鍵 Lightsailコンソールからキーペアをダウンロード、pemファイルの中身をそのまま登録

注: GITHUB_TOKEN はGitHub Actionsによって自動的に提供されるため、手動での設定は不要です。

6. さいごに

Lightsail + GitHub Actionsの構成で、月額55ドルからの低コストで自動デプロイ環境を構築できました。

ただし、Lightsailにはいくつかの制限があります。スケーリングの柔軟性がEC2/ECSより低く、大規模トラフィックには不向きです。また、VPC内の他のAWSサービスとの連携にも制約があります。

この構成はPoC・開発環境に最適です。本番環境への展開を検討する際は、トラフィック量やスケール要件を踏まえて、EC2/ECSへの移行も視野に入れることをおすすめします。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?