DokkuをVPSをインストールし、Production, Staging, PRごとの環境・CDを構築
はじめに
私は普段Ruby on Railsを使用してWebアプリケーションを作成しています。
個人でもちょっとしたものをデプロイしたいと思ったときに、HerokuのようなPaaSが使えると思います。
ただ、細かく環境を分けたりなど数が増えると、それに応じて料金が発生して気軽に試せなくなりそうです。
そこで、DokkuをVPSへインストールし、Production, Staging, PRごとの環境・CD(Github Actions)を構築してみました。
Dokkuとは?
Dokkuは、セルフホスティングできるHerokuのようなPaaSのオープンソースソフトウェアです。
HerokuのBuildpack(Herokuish Buildpacks)を使用しているので、Ruby on RailsなどのWebアプリケーションが簡単にデプロイできます。
An open source PAAS alternative to Heroku.
Dokku is an extensible, open source Platform as a Service that runs on a single server of your choice. Dokku supports building apps on the fly from a git push via either Dockerfile or by auto-detecting the language with Buildpacks, and then starts containers based on your built image. Using technologies such as nginx and cron, Web processes are automatically routed to, while background processes and automated cron tasks are also managed by Dokku.
環境
- VPS: Ubuntu 22.04 LTS
- Dokku 0.31.0
Production, Staging, PR Review環境について
Production
- 本番用のWebアプリケーション・データベース
- Gitのブランチは
main
- 他環境の影響を受けないように、ひとつのVPSで共存しない
Staging
- テスト用のWebアプリケーション・データベース
- Gitのブランチは
develop
- Productionと同様の環境で、スペックなどは落とす
- PR Reviewと同じVPSに共存
PR(Pull Request) Review
- Pull Requestで変更を加えたWebアプリケーション
- Gitのブランチは、それぞれのPRブランチ
- データベースはStagingへ接続
- Stagingと同じVPSに共存
構成図
Dokkuのインストール
こちらを参考に、Dokkuをインストールします。
Webアプリケーションのデプロイ
こちらを参照に、Production, Staging環境ごとのWebアプリケーションを作成・デプロイします。
簡単な流れは以下の通りです。
-
dokku apps:create
でデプロイ先のアプリケーションを作成 -
dokku postgres:create
でデータベースを作成 -
dokku postgres:link
でアプリケーションからデータベースを参照できるようにする -
git push
でアプリケーションに対してデプロイ - Domain, SSL(Let's Encrypt)を設定
※ Production, Stagingを区別できるようにsuffixなどを付けるとよいと思います。
途中経過
ここまでで、Production, Staging環境ごとにWebアプリケーションが手動でデプロイできる状態になったと思います。
ここからは、Github Actionsを使用して、自動でデプロイ(CD)できるようにしていきます。
Github Actions Staging環境へのデプロイ設定
Staging環境へのデプロイは、developブランチへプッシュ・マージされたときに自動で行います。
設定ファイルは以下のようなものになります。
.github/workflows/deploy_staging.yml
name: Deploy Staging
on:
push:
branches: [develop]
jobs:
deploy:
name: Deploy Staging
runs-on: ubuntu-latest
steps:
- name: Cloning repo
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Push to dokku
uses: dokku/github-action@v1.4.0
with:
git_remote_url: ${{ vars.DOKKU_GIT_REMOTE_URL_FOR_STAGING }}
ssh_private_key: ${{ secrets.DOKKU_SSH_PRIVATE_KEY_FOR_STAGING }}
branch: 'main'
git_push_flags: '--force'
Github Actions Production環境へのデプロイ設定
次にProduction環境へのデプロイ設定ファイルを作成します。
内容はStaging環境とほぼ同じで、対象のブランチやGitのプッシュ先やキーを変更します。
.github/workflows/deploy_staging.yml
name: Deploy Production
on:
push:
branches: [develop]
jobs:
deploy:
name: Deploy Production
runs-on: ubuntu-latest
steps:
- name: Cloning repo
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Push to dokku
uses: dokku/github-action@v1.4.0
with:
git_remote_url: ${{ vars.DOKKU_GIT_REMOTE_URL_FOR_PRODUCTION }}
ssh_private_key: ${{ secrets.DOKKU_SSH_PRIVATE_KEY_FOR_PRODUCTION }}
branch: 'main'
git_push_flags: '--force'
Github Actions PR Review環境へのデプロイ設定
最後にPR Review環境へのデプロイ設定を作成します。
こちらに関しては、動作確認したいときにアプリケーションの作成・デプロイし、PRが閉じたときにはアプリケーションを削除します。
PR Review環境へのデプロイ設定
- PRに
/deploy_pr_review
とコメントしたときにデプロイを実行-
xt0rted/slash-command-action
を使用し、スラッシュコマンドでデプロイを実行
-
- Domain, SSL(Let's Encrypt)を設定
-
bin/ci-pre-deploy
ファイルを作成することで、デプロイ時に自動実行される -
domains:add
でDomainを設定 -
letsencrypt:enable
でSSLを設定-
letsencrypt:enable
は設定済みでもスキップされない - Let's Encryptの制限対象にならないように、設定済みの場合はスキップする
-
-
-
Deploy PR Review
ラベルを追加- デプロイ済みかラベルで判断
.github/workflows/deploy_pr_review.yml
name: Deploy PR Review
on:
issue_comment:
types: [created, edited]
concurrency:
group: deploy_pr_review
jobs:
check_slash_command:
name: Check slash command
runs-on: ubuntu-latest
if: ${{ github.event.issue.pull_request }}
outputs:
command_name: ${{ steps.slash_command_action.outputs.command-name }}
command_arguments: ${{ steps.slash_command_action.outputs.command-arguments }}
outcome: ${{ steps.slash_command_action.outcome }}
target_branch_name: ${{ steps.get_branch_name.outputs.result }}
steps:
- name: Check for Command
id: slash_command_action
continue-on-error: true
uses: xt0rted/slash-command-action@v2
with:
command: deploy_pr_review
- name: Get branch name
id: get_branch_name
if: ${{ steps.slash_command_action.outcome == 'success' }}
uses: actions/github-script@v7
with:
script: |
const pull_request = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number
})
return pull_request.data.head.ref
result-encoding: string
deploy:
name: Deploy PR Review
runs-on: ubuntu-latest
needs: check_slash_command
if: ${{ needs.check_slash_command.outputs.outcome == 'success' }}
steps:
- name: Cloning repo
uses: actions/checkout@v3
with:
fetch-depth: 0
ref: ${{ needs.check_slash_command.outputs.target_branch_name }}
- name: Set ENV
run: |
DOKKU_REVIEW_APP_NAME=${{ vars.DOKKU_REVIEW_APP_NAME_PREFIX }}${{ github.event.issue.number }}
PR_REVIEW_DOMAIN=${{ vars.REVIEW_APP_DOMAIN_PREFIX }}${{ github.event.issue.number }}.${{ vars.STAGING_DOMAIN }}
PR_REVIEW_URL=https://$PR_REVIEW_DOMAIN
echo "DOKKU_REVIEW_APP_NAME=$DOKKU_REVIEW_APP_NAME" >> $GITHUB_ENV
echo "PR_REVIEW_DOMAIN=$PR_REVIEW_DOMAIN" >> $GITHUB_ENV
echo "PR_REVIEW_URL=$PR_REVIEW_URL" >> $GITHUB_ENV
- name: Create bin/ci-pre-deploy file
run: |
cat << EOF > bin/ci-pre-deploy
#!/bin/sh -l
if [ "\$IS_REVIEW_APP" = "true" ]; then
ssh "\$SSH_REMOTE" -- domains:add "\$APP_NAME" "$PR_REVIEW_DOMAIN"
ssh "\$SSH_REMOTE" -- ps:scale "\$APP_NAME" web=1 --skip-deploy
LETSENCRYPT_LIST_COUNT=\`ssh "\$SSH_REMOTE" -- letsencrypt:list | grep "\$APP_NAME" | wc -l\`
if [ "\$LETSENCRYPT_LIST_COUNT" = "0" ]; then
ssh "\$SSH_REMOTE" -- letsencrypt:enable "\$APP_NAME"
else
echo "skip letsencrypt:enable"
fi
echo "configured the review app"
fi
EOF
cat bin/ci-pre-deploy
- name: Push to dokku
uses: dokku/github-action@v1.4.0
with:
command: review-apps:create
git_remote_url: ${{ vars.DOKKU_GIT_REMOTE_URL_FOR_STAGING }}
ssh_private_key: ${{ secrets.DOKKU_SSH_PRIVATE_KEY_FOR_STAGING }}
branch: 'main'
git_push_flags: '--force'
review_app_name: ${{ env.DOKKU_REVIEW_APP_NAME }}
- name: Add Label
uses: actions/github-script@v6
with:
script: |
github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ["Deploy PR Review"]
})
- name: Post deploied comment
uses: actions/github-script@v6
env:
MESSAGE: |
Deploied PR Review: ${{ env.PR_REVIEW_URL }}
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: process.env.MESSAGE
})
PR Review環境のアプリケーション削除設定
- PRをマージ・クローズしたときにアプリケーションを削除
-
Deploy PR Review
ラベルがついていない場合、スキップする - アプリケーション削除後、
Deploy PR Review
ラベルを外す
.github/workflows/drop_pr_review.yml
name: Drop PR Review
on:
pull_request:
types: [closed]
jobs:
drop_pr_review:
name: Drop PR Review
runs-on: ubuntu-latest
steps:
- name: Check for label
id: check_for_label
uses: actions/github-script@v6
with:
script: |
const response = await github.rest.issues.listLabelsOnIssue({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
})
const hasLabel = (response.data ?? []).some(label => label.name === 'Deploy PR Review')
if (hasLabel) {
return 'do_not_skip'
}
return 'skip'
result-encoding: string
- name: Set ENV
if: steps.check_for_label.outputs.result != 'skip'
run: |
DOKKU_REVIEW_APP_NAME=${{ vars.DOKKU_REVIEW_APP_NAME_PREFIX }}${{ github.event.pull_request.number }}
echo "DOKKU_REVIEW_APP_NAME=$DOKKU_REVIEW_APP_NAME" >> $GITHUB_ENV
- name: Drop PR Review
if: steps.check_for_label.outputs.result != 'skip'
uses: dokku/github-action@v1.4.0
with:
command: review-apps:destroy
git_remote_url: ${{ vars.DOKKU_GIT_REMOTE_URL_FOR_STAGING }}
ssh_private_key: ${{ secrets.DOKKU_SSH_PRIVATE_KEY_FOR_STAGING }}
review_app_name: ${{ env.DOKKU_REVIEW_APP_NAME }}
- name: Remove Label
if: steps.check_for_label.outputs.result != 'skip'
uses: actions/github-script@v6
with:
script: |
github.rest.issues.removeLabel({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
name: ["Deploy PR Review"]
})
- name: Post droped comment
if: steps.check_for_label.outputs.result != 'skip'
uses: actions/github-script@v6
env:
MESSAGE: |
Droped PR Review
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: process.env.MESSAGE
})
まとめ
DokkuをVPSをインストールし、Production, Staging, PRごとの環境・CDを構築してみました。
Webアプリケーションをデプロイしたい、Production環境だけでなくStaging環境もほしいなどあれば、Dokkuを使用してみてはいかがでしょうか。
Discussion