4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

NestJS・MySQL・Prisma・Dockerを使用してAWS EC2へGitLab CI/CDでデプロイする方法

4
Last updated at Posted at 2026-03-19

NestJS・MySQL・Prisma・Dockerを使用してAWS EC2へGitLab CI/CDでデプロイする方法

ソフトウェア開発において、アプリケーションを手動でサーバーへデプロイする作業は時間がかかり、ミスが発生しやすいです。そのため、**CI/CD(Continuous Integration / Continuous Deployment)**によるデプロイの自動化は、作業効率の向上とシステムの安定性確保において非常に重要です。

本記事では、NestJSアプリケーションをAWS EC2Dockerを用いてデプロイし、GitLab CI/CDを使ってtest → build → deployまでの一連の流れを自動化する、完全なCI/CDパイプラインの構築方法を解説します。


1. システム構成

本記事で扱うシステムは、以下の主要コンポーネントで構成されています:

  • AWS EC2(Ubuntu):アプリケーションを実行するサーバー

    • RAM: 8GB
    • Storage: 30GB
  • NestJS:APIを構築するバックエンドフレームワーク。

  • Prisma:MySQLデータベースと連携するためのORM。

  • MySQL:データベース管理システム。

  • Nginx:トラフィックをアプリケーションへ振り分けるリバースプロキシ。

  • GitLab CI/CD:ビルドおよびデプロイの自動化。

  • Docker & Docker Compose:各サービスをコンテナとしてパッケージ化・管理。

全体アーキテクチャ

Developer
   │
   │ push code
   ▼
GitLab Repository
   │
   │ GitLab CI/CD Pipeline
   ▼
Build Docker Image
   │
   ▼
GitLab Container Registry
   │
   ▼
AWS EC2
   │
   └── docker compose pull + up
           │
           ├── NestJS App
           ├── MySQL
           ├── Nginx
           └── phpMyAdmin

2. ソースコードの準備

サンプル用のリポジトリを用意しています。こちらからご利用ください:

https://gitlab.com/vmhfpt/nestjs-project-deploy/-/tree/main

まずは、ローカル環境にクローンします:

git clone https://gitlab.com/vmhfpt/nestjs-project-deploy.git

クローンが完了したら、以下の手順を進めてください:

  1. 自身の GitLab アカウントで新しいリポジトリを作成します。
  2. 作成したリポジトリにソースコードを Push し、CI/CD の設定を開始しましょう。

3. AWS で EC2 インスタンスを作成する

詳細な手順については、以下のガイドを参考にしてください:
https://qiita.com/tuanphan/items/56cba7619ca0e3cf0d4d

まずは AWS コンソール → EC2 → インスタンスを起動 にアクセスします。

推奨構成(設定例):

  • OS: Ubuntu
  • インスタンスタイプ: t2.large
  • メモリ (RAM): 8GB
  • ストレージ: 30GB

Screenshot 2026-03-17 at 08.23.48.png

Screenshot 2026-03-17 at 08.24.52.png

インスタンスの作成が完了したら、SSH でサーバーに接続します:

chmod 400 "nestjs.pem"
ssh -i "nestjs.pem" ubuntu@ec2-54-196-78-128.compute-1.amazonaws.com

Screenshot 2026-03-17 at 08.33.11.png

4. EC2上の環境構築

Dockerのインストール

詳細な手順はこちらを参照してください:

-> 注意:後々の権限(permission)エラーを防ぐため、sudoなしでDockerを実行できるように設定してください。

簡単な確認:

docker -v
docker ps -a

GitLab Runnerのインストール

GitLab Runnerは、パイプライン内のジョブを実行する役割を担うコンポーネントです。

sudo apt update
curl -L [https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh](https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh) | sudo bash
sudo apt install gitlab-runner -y

確認:

gitlab-runner --version

Screenshot 2026-03-17 at 08.50.32.png

その後、GitLabプロジェクトに**ランナーを登録(register)**します。
以下のコマンドを実行します:

sudo gitlab-runner register

その後、GitLabから要求される情報を入力します:

Project
→ Settings
→ CI/CD
→ Runners
→ New project runner

GitLab上でランナーを1つ作成し、トークンをコピーしてターミナルに貼り付けます。

Screenshot 2026-03-17 at 08.55.35.png

  • Runner name: (空白のまま)Enterキーを押します。
  • Runner description: (空白のまま)Enterキーを押します。
  • Executor: docker と入力します。
  • Please enter the default Docker image (e.g. ruby:2.7): node:20 と入力します。

ステータスを確認し、GitLab RunnerがGitLabに正常に接続されていることを確認します。

sudo gitlab-runner status
sudo gitlab-runner verify

Screenshot 2026-03-17 at 09.05.05.png


デプロイ用のSSHキーの作成

SSHキーを作成します:

ssh-keygen -t ed25519 -C "hung.vu@tomosia.com"

サーバー自身を「許可」するための設定。

スクリプトはSSHを使用してそのサーバー自体(または別のサーバー)を制御するため、公開鍵(Public Key)を信頼できるリストに追加する必要があります:

cat ~/.ssh/id_ed25519.pub >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

秘密鍵(Private Key)を取得してGitLabに貼り付ける

cat ~/.ssh/id_ed25519

CI/CDへの追加方法:

  • その内容をすべてコピーします(BEGIN行とEND行を含みます)。
  • GitLab Project -> Settings -> CI/CD -> Variables に移動します。
  • Add Variable をクリックします:
  • Key: DEPLOY_SSH_KEY
  • Value: 先ほどコピーした内容を貼り付けます
  • Type: Fileを選択します(スクリプトで chmod 600 $DEPLOY_SSH_KEY コマンドを使用するため、これは非常に重要です。このコマンドは、変数がランナー上の物理ファイルである場合にのみ機能します)

Screenshot 2026-03-17 at 09.15.04.png

Screenshot 2026-03-17 at 09.16.07.png

-> 注意:キーを貼り付けた後、エラーを防ぐために最後に空白行を追加してください。


サーバー上での設定ファイルの準備

以下のファイルを準備する必要があります:

nginx/default.conf
docker-compose.yml
.env

デプロイ用ディレクトリの作成:

mkdir -p /home/ubuntu/app
cd /home/ubuntu/app

1. Nginxのセットアップ

mkdir nginx
vim nginx/default.conf

Vimの使い方:

  • 挿入モード(Insert)に入るには i を押します。
  • 保存してVimを終了するには: Esc を押してから :wq! と入力してEnterを押します。

以下の内容を貼り付けて保存します\

server {
    listen 80;

    location / {
        proxy_pass http://app:3000;
        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;
    }
}

2. docker-compose.ymlのセットアップ

vim docker-compose.yml
version: "3.9"

services:

  mysql:
    image: mysql:8
    container_name: mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: nestdb
      MYSQL_USER: nest
      MYSQL_PASSWORD: nest123
    ports:
      - "3307:3306"
    volumes:
      - mysql_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-proot"]
      interval: 5s
      timeout: 5s
      retries: 10
      start_period: 10s

  nginx:
    image: nginx:latest
    container_name: nginx_proxy
    ports:
      - "80:80"
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
    depends_on:
      - app
    restart: always

  phpmyadmin:
    image: phpmyadmin/phpmyadmin
    container_name: phpmyadmin
    restart: always
    ports:
      - "8080:80"
    environment:
      PMA_HOST: mysql
      PMA_PORT: 3306
    depends_on:
      - mysql

  app:
    image: ${CI_REGISTRY_IMAGE}:latest
    container_name: nest-app
    restart: always
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: "mysql://nest:nest123@mysql:3306/nestdb?allowPublicKeyRetrieval=true"
    depends_on:
      mysql:
        condition: service_healthy

volumes:
  mysql_data:

3. .envのセットアップ

vim .env
DATABASE_URL="mysql://root:root@localhost:3307/nestdb"

ディレクトリの概要:

Screenshot 2026-03-17 at 09.34.09.png


必要なポートの開放

**EC2のセキュリティグループ(Security Group)**で以下のポートを開放します:

  • 時的にすべてのトラフィック(All traffic)を許可するルールを1つ作成します
Port Purpose
80 Nginx
8080 phpMyAdmin
3307 MySQL(リモートアクセスが必要な場合

Screenshot 2026-03-17 at 09.41.16.png

Screenshot 2026-03-17 at 09.42.43.png

Screenshot 2026-03-17 at 09.45.51.png


5. GitLab CI/CD 変数の設定

GitLab上:

Settings → CI/CD → Variables

以下の変数を追加します:

DEPLOY_HOST
DEPLOY_USER
DEPLOY_PATH

上記3つの環境変数すべてに対して、Type、Environments、Visibility、Flagsを設定します。

Screenshot 2026-03-17 at 09.55.11.png

  • 対応する値(value)の取得元

DEPLOY_HOST
Screenshot 2026-03-17 at 09.59.06.png

DEPLOY_USER
Screenshot 2026-03-17 at 10.01.45.png

DEPLOY_PATH

/home/ubuntu/app

-> 注意:サーバー上のデプロイ先ディレクトリと一致する必要があります。

事前に実行したコマンド:

mkdir -p /home/ubuntu/app

6. GitLab CI/CD パイプラインの設定

ファイルの作成:

.gitlab-ci.yml

パイプラインは3つのステージで構成されます:

stages:
  - test
  - build
  - deploy

GitLab CI/CD Pipeline

# GitLab CI/CD for Nest app (Docker + docker-compose + docker-entrypoint.sh)
#
# Required CI/CD variables for deploy (Settings > CI/CD > Variables):
#   DEPLOY_SSH_KEY  - Private SSH key to access the deploy server (type: File or Variable, protected)
#   DEPLOY_HOST     - Deploy server hostname or IP
#   DEPLOY_USER    - SSH user on deploy server
#   DEPLOY_PATH    - Path on server where docker-compose runs (must use app image from registry)
#
# On deploy server: ensure docker-compose uses image from registry, e.g.:
#   app: image: ${CI_REGISTRY_IMAGE}:latest  (and run docker login before compose)

stages:
  - test
  - build
  - deploy

variables:
  NODE_VERSION: "20"

# -----------------------------------------------------------------------------
# CI: Lint, build, test (unit tests; no DB required)
# -----------------------------------------------------------------------------
test:
  stage: test
  image: node:${NODE_VERSION}
  tags:
    - ec2-node-runner
  cache:
    key: ${CI_COMMIT_REF_SLUG}-npm
    paths:
      - node_modules/
  before_script:
    - npm ci
    - npx prisma generate
  script:
    - npm run lint
    - npm run build
    - npm run test
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

# -----------------------------------------------------------------------------
# CD: Build Docker image and push to GitLab Container Registry
# -----------------------------------------------------------------------------
build:
  stage: build
  image: docker:24
  services:
    - docker:24-dind
  variables:
    DOCKER_TLS_CERTDIR: "/certs"
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA -t $CI_REGISTRY_IMAGE:latest .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    - docker push $CI_REGISTRY_IMAGE:latest
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

# -----------------------------------------------------------------------------
# CD: Deploy (example: pull image and run with docker-compose on a runner/server)
# Configure CI/CD variables: DEPLOY_SSH_KEY, DEPLOY_HOST, etc. as needed.
# -----------------------------------------------------------------------------
deploy:
  stage: deploy
  image: alpine:latest
  before_script:
  - apk add --no-cache openssh-client
  - eval $(ssh-agent -s)
  - chmod 600 $DEPLOY_SSH_KEY
  - cat $DEPLOY_SSH_KEY
  - ssh-add $DEPLOY_SSH_KEY
  - mkdir -p ~/.ssh
  - chmod 700 ~/.ssh
  - ssh-keyscan -H $DEPLOY_HOST >> ~/.ssh/known_hosts 2>/dev/null || true
  script:
    - |
      ssh $DEPLOY_USER@$DEPLOY_HOST "
        cd $DEPLOY_PATH &&
        echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY &&
        export CI_REGISTRY_IMAGE=$CI_REGISTRY_IMAGE &&
        docker compose pull &&
        docker compose up -d
      "
  environment:
    name: production
    url: https://$DEPLOY_HOST
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH


7. Dockerfileの準備

# Dockerfile
FROM node:20

WORKDIR /app

COPY package*.json ./

RUN npm install

COPY . .

RUN npx prisma generate

RUN npm run build

COPY docker-entrypoint.sh /app/docker-entrypoint.sh
RUN chmod +x /app/docker-entrypoint.sh

EXPOSE 3000

ENTRYPOINT ["/app/docker-entrypoint.sh"]


docker-entrypoint.sh

# docker-entrypoint.sh
#!/bin/sh
set -e

# Generate Prisma client (already done at build, safe to run again)
npx prisma generate

# Apply schema: use migrations if present, otherwise db push (e.g. when prisma/migrations is not in image)
if [ -d "prisma/migrations" ] && [ -n "$(find prisma/migrations -maxdepth 2 -name 'migration.sql' 2>/dev/null)" ]; then
  npx prisma migrate deploy
else
  npx prisma db push
fi

# Run seed (skip failure so app still starts if DB is not ready or already seeded)
npm run prisma:seed || true

exec node dist/src/main.js


docker-compose.yml

# docker-compose.yml
version: "3.9"

services:

  mysql:
    image: mysql:8
    container_name: mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: nestdb
      MYSQL_USER: nest
      MYSQL_PASSWORD: nest123
    ports:
      - "3307:3306"
    volumes:
      - mysql_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-proot"]
      interval: 5s
      timeout: 5s
      retries: 10
      start_period: 10s

  nginx:
    image: nginx:latest
    container_name: nginx_proxy
    ports:
      - "80:80"
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
    depends_on:
      - app
    restart: always

  phpmyadmin:
    image: phpmyadmin/phpmyadmin
    container_name: phpmyadmin
    restart: always
    ports:
      - "8080:80"
    environment:
      PMA_HOST: mysql
      PMA_PORT: 3306
    depends_on:
      - mysql

  app:
    build: .
    container_name: nest-app
    restart: always
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: "mysql://nest:nest123@mysql:3306/nestdb?allowPublicKeyRetrieval=true"
    depends_on:
      mysql:
        condition: service_healthy

volumes:
  mysql_data:


9. GitLabのネームタグ設定

  • GitLabの画面を開く -> プロジェクトを選択 -> CI/CD設定(CI/CD setting) -> 作成したランナーを選択 -> 編集(edit)

-> 注意:name tagは .gitlab-ci.yml ファイルの tags キーと一致する必要があります。

Screenshot 2026-03-17 at 11.31.33.png


8. 結果の確認

mainブランチにコードをプッシュ(push)すると、GitLab Pipelineが自動的に実行されます。

Screenshot 2026-03-19 at 10.55.05.png

以下の場所から進捗を確認できます:

GitLab → Build → Pipelines

Screenshot 2026-03-17 at 10.32.51.png

Screenshot 2026-03-17 at 10.34.09.png

デプロイが成功した後、以下にアクセスします:

http://EC2_IP

Screenshot 2026-03-17 at 10.35.43.png

アプリケーションの動作確認を行います。

-> 注意:https から http に変更してください。
例:https://your-ip -> http://your-ip

結果:

http://your-ip
Screenshot 2026-03-17 at 10.42.16.png

http://your-ip:8080 (phpMyAdmin)

Screenshot 2026-03-17 at 10.43.54.png

Screenshot 2026-03-17 at 10.40.19.png

http://your-ip/posts (ランダムなPostsを取得)
Screenshot 2026-03-17 at 10.42.50.png


結論

完全なCI/CDパイプラインの構築に成功しました:

Code
 ↓
Test
 ↓
Build Docker Image
 ↓
Push Registry
 ↓
Deploy to EC2

Dockerを使用することで開発(development)環境と本番(production)環境の同一性を保証し、同時にGitLab CI/CDがデプロイプロセス全体を自動化してくれます。

参考リポジトリ:

[https://gitlab.com/vmhfpt/nestjs-project-deploy/-/tree/main](https://gitlab.com/vmhfpt/nestjs-project-deploy/-/tree/main)

実際のデプロイプロセスにおいて、例えば以下のようなよくあるエラーに遭遇する可能性があります:

Permission denied (publickey)

これは通常、GitLab RunnerとEC2サーバー間のSSH設定に関連するエラーです。接続が正常に確立されていることを確認するために、SSHキー、アクセス権限(authorized_keys)、およびGitLab CI/CDの環境変数の設定を再確認してください。

この記事が、実践的なCI/CDシステムの構築方法をより深く理解し、ご自身のプロジェクトに効果的に適用する助けになれば幸いです。

4
3
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
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?