Activity, over time
Daily observations from the public event archive.
Attention
Daily stars & forks
Star accumulation
Cumulative stars
Pull requests
Daily opens & merges
Conversations
Daily issues & comments
Community
Daily accounts & reviews
Releases
Daily releases
Trending history
Daily top 10 · last 90 days · UTC
Mentions
Stars
Forks
Contributors
Latest stargazers
The last 10 distinct accounts we observed this year.
Most active participants
PR, issue, comment & review events in the past year.
Packages
No packages found.
Live mentions badge
Show how many posts mention your project in the last 30 days.
Live mentions · last 30 days
Mentions
メモリに余裕のない VPS へ GitHub Actions と GHCR で自動デプロイする(ビルドはランナー、VPS は pull だけ) この記事でできること GitHub Actions と GitHub Container Registry(GHCR)で、VPS への自動デプロイを作ります。main ブランチに push すると、GitHub Actions のランナーで Docker イメージをビルドして GHCR に push し、VPS ではそのイメージを pull して入れ替えるだけです。私の環境では、ビルドのキャッシュが使えるときは push してから約1分でサイトに反映されます。題材は VPS で公開している Next.js のポートフォリオサイトです。 VPS のメモリに余裕がなく、VPS の上ではビルドしたくない人に向けて書いています。GitHub Actions と Docker Compose の基本は知っている前提です。 前提と環境 2026年10月に構築したときの環境です。 | 項目 | 内容 | |---|---| | VPS | Ubuntu 26.04 LTS、x86_64、メモリ 7.7GB | | VPS のソフトウェア | Docker と Docker Compose、nginx(リバースプロキシ)、certbot(HTTPS) | | アプリ | Next.js 16.4(output: "standalone")、イメージのベースは node:24-alpine | | 手元 | Apple Silicon の Mac | | Actions | actions/checkout@v4、docker/setup-buildx-action@v3、docker/login-action@v3、docker/build-push-action@v6、appleboy/ssh-action@v1 | VPS では、ほかのサービス(ゲームサーバーなど)がメモリを約4GB 使っていて、空きは約3GB です。Next.js のビルドでほかのサービスに影響を出したくないので、VPS ではビルドしないことにしました。 nginx は portfolio.example.com へのリクエストを 127.0.0.1:3000 のコンテナへ流すように設定してあります。nginx と HTTPS の設定は、この記事では扱いません。 記事の中の名前は次のように置き換えています。 - ドメイン: example.com - GitHub のアカウント: takibito - VPS への SSH: ~/.ssh/config に Host vps として書いてある前提で ssh vps 構成 git push(main) │ ▼ GitHub Actions ├ build-and-push:linux/amd64 でビルドし、GHCR に push │ ghcr.io/takibito/portfolio:latest と :<コミットの SHA> ▼ └ deploy:SSH で VPS に入り、docker compose pull && docker compose up -d │ ▼ https://portfolio.example.com に反映 ジョブは build-and-push と deploy の2つに分けています。deploy には needs: build-and-push を書き、ビルドと push が成功したときだけ動くようにしています。 自動化する前の手動デプロイ 自動化する前は、Mac でイメージをビルドし、docker save の出力を ssh で VPS の docker load に流し込んでいました。Mac は Apple Silicon(arm64)で VPS は x86_64 なので、--platform linux/amd64 を付けてビルドします。 docker buildx build --platform linux/amd64 -t portfolio:latest --load . docker save portfolio:latest | gzip | ssh vps 'gunzip | docker load' ssh vps 'cd ~/portfolio && docker compose up -d && docker image prune -f' この方法ならレジストリがなくても公開できますが、更新のたびに Mac でビルドしてイメージを送る必要がありました。この3行を GitHub Actions に置き換えます。手動の方法は、Actions が止まったときのために今も使えるようにしてあります。 手順 1. デプロイ用の SSH 鍵を作って VPS に登録する GitHub Actions から VPS に入るための専用の鍵を作ります。普段使っている鍵と分けておくと、デプロイ用の鍵だけを消したり作り直したりできます。 ssh-keygen -t ed25519 -f ~/.ssh/portfolio_deploy -C "gha-deploy portfolio" -N "" ssh vps 'cat >> ~/.ssh/authorized_keys' < ~/.ssh/portfolio_deploy.pub pbcopy < ~/.ssh/portfolio_deploy # 秘密鍵をクリップボードへ(次の手順で Secrets に貼る) sshd_config の AuthorizedKeysFile で鍵のファイルを変えている場合は、そのファイルに追記します。 2. GitHub の Secrets を登録する リポジトリの Settings → Secrets and variables → Actions で、次の3つを登録します。 | Secret 名 | 値 | |---|---| | SSH_HOST | VPS のホスト名 | | SSH_USER | VPS で docker を実行できるユーザー | | SSH_KEY | 手順1で作った秘密鍵の中身(-----BEGIN の行から -----END の行まで) | GHCR に push するためのトークンは登録しません。ワークフローに自動で発行される GITHUB_TOKEN に packages: write の権限を与えて使います[^ghcr]。 3. VPS の docker-compose.yml を GHCR のイメージに切り替える 手動デプロイのときは、docker load したイメージ portfolio:latest を使っていました。docker-compose.yml を書き換えて、GHCR のイメージを pull するようにします。 services: portfolio: image: ghcr.io/takibito/portfolio:latest pull_policy: always ports: - "127.0.0.1:3000:3000" restart: unless-stopped mem_limit: 512m pull_policy: always にすると、Compose は起動のたびにレジストリからイメージを pull します[^compose]。mem_limit はコンテナが使えるメモリの上限で、ほかのサービスのメモリを圧迫しないように 512MB にしています。ポートは 127.0.0.1 にだけ公開し、外からは nginx を通して届くようにしています。 この時点では GHCR にまだイメージがないので、docker compose up はしません。 4. ワークフローを追加して push する .github/workflows/deploy.yml を作ります。 name: Deploy portfolio on: push: branches: [main] workflow_dispatch: # 手動実行も可能にする concurrency: group: deploy-portfolio cancel-in-progress: true # 連続 push 時は最新だけデプロイ env: IMAGE: ghcr.io/takibito/portfolio jobs: build-and-push: runs-on: ubuntu-latest permissions: contents: read packages: write # GHCR へ push するため steps: - uses: actions/checkout@v4 - uses: docker/setup-buildx-action@v3 - name: Log in to GHCR uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push uses: docker/build-push-action@v6 with: context: . platforms: linux/amd64 push: true tags: | ${{ env.IMAGE }}:latest ${{ env.IMAGE }}:${{ github.sha }} cache-from: type=gha cache-to: type=gha,mode=max deploy: needs: build-and-push runs-on: ubuntu-latest steps: - name: Deploy over SSH uses: appleboy/ssh-action@v1 with: host: ${{ secrets.SSH_HOST }} username: ${{ secrets.SSH_USER }} key: ${{ secrets.SSH_KEY }} script: | cd ~/portfolio docker compose pull docker compose up -d docker image prune -f - concurrency の cancel-in-progress: true は同じグループで実行中のワークフローを取り消す設定です[^concurrency]。続けて push したときは、最後の push だけがデプロイされます。 - イメージには latest とコミットの SHA の2つのタグを付けます。SHA のタグはあとで特定の版に戻すときに使います。 - cache-to の type=gha でビルドのキャッシュを GitHub Actions のキャッシュに保存し、cache-from で次のビルドのときに読み込みます[^cache]。 - イメージ名は小文字で書きます。イメージ名に使えるのは英小文字・数字・区切り文字だけです[^ref]。 2026年10月にこのワークフローを動かしたときは、警告が出ました[^node20]。actions/checkout@v4、docker/setup-buildx-action@v3、docker/login-action@v3、docker/build-push-action@v6 の4つが Node.js 20 向けのため、Node.js 24 で動かしたという内容です。ジョブは成功しましたが、これから使う場合は、各アクションの新しいメジャーバージョンを確かめてください。 Next.js の Dockerfile は output: "standalone" の出力[^standalone]を使う3段のマルチステージビルドにしています。 :::details Dockerfile # syntax=docker/dockerfile:1 FROM node:24-alpine AS deps WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci FROM node:24-alpine AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . ENV NEXT_TELEMETRY_DISABLED=1 RUN npm run build FROM node:24-alpine AS runner WORKDIR /app ENV NODE_ENV=production NEXT_TELEMETRY_DISABLED=1 PORT=3000 HOSTNAME=0.0.0.0 RUN addgroup -S nodejs && adduser -S nextjs -G nodejs COPY --from=builder /app/public ./public COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static USER nextjs EXPOSE 3000 CMD ["node", "server.js"] ::: ワークフローは push した時点で動くので、Secrets と VPS 側の準備が済んでから push します。 git add .github/workflows/deploy.yml git commit -m "ci: GitHub Actions による自動デプロイを追加" git push -u origin main 5. GHCR のパッケージの公開範囲を確かめる 初めての push で、GHCR にパッケージ portfolio ができます。GitHub のドキュメントには、初めて公開したパッケージは Private になり、リポジトリと紐付いていても公開範囲は引き継がれないと書かれています[^visibility]。 ただ、私の場合は公開範囲の設定を変えていないのに、パッケージは最初から Public で、ログインしなくても pull できる状態でした。個人アカウントの公開リポジトリから GITHUB_TOKEN で push した場合です。ドキュメントと違う結果なので、初めて push したあとは、パッケージのページで公開範囲を確かめてください。 !GHCR のパッケージのページ。Public と表示され、latest とコミットの SHA の2つのタグが付いている Private だった場合は、Public にすれば VPS から匿名で pull できるので[^ghcr]、VPS で docker login をしなくて済みます。公開範囲は GitHub のプロフィールの Packages → portfolio → Package settings → Change visibility で変えられます。一度 Public にしたパッケージは Private に戻せないので[^visibility]、公開してよいイメージかを先に確かめてください。 Private のままにする場合は、read:packages だけを付けたアクセストークンを作り、VPS で一度だけ docker login ghcr.io をしておきます。 6. 動作確認 push したあとは、次の3点を確かめます。 - GitHub の Actions タブで、build-and-push と deploy の2つのジョブが成功している - VPS で docker compose ps を実行すると、コンテナが動いている - curl -I https://portfolio.example.com が 200 を返す !GitHub Actions の実行結果。build-and-push が45秒、deploy が16秒で、どちらも成功している 私の環境では、ワークフローを追加した最初の push は、ビルドのキャッシュがないので2分50秒かかりました。そのあと小さな変更を1つ push すると、キャッシュが使えて1分8秒で終わり、サイトにも反映されました(上の画面)。これ以降は main に push するだけで公開されます。 最初の push の前に確かめること ワークフローのファイルをコミットしたか 最初の push では、ワークフローが動きませんでした。.github/ などのファイルをコミットしておらず、GitHub には Create Next App の最初のコミットしか届いていなかったのが原因です。git status で未コミットのファイルが残っていないかを確かめてから push してください。 空の public/ が Git に入っているか Dockerfile の最後の段では COPY --from=builder /app/public ./public で public/ をコピーします。私は Create Next App が置いた SVG を消していたので、public/ は空でした。Git は空のフォルダを記録しないため、そのまま push すると、ランナーで checkout したソースには public/ がなく、この COPY が失敗します。Mac でビルドしていたときは手元に空の public/ があったので、問題になりませんでした。ワークフローを push する前に気づき、public/.gitkeep を追加しました。 deploy ジョブの終了ステータス deploy のスクリプトは、途中の cd や docker compose pull が失敗しても、最後の docker image prune -f が成功すればジョブ全体が成功になります。シェルのスクリプトの終了ステータスは、最後に実行したコマンドのステータスになるためです。appleboy/ssh-action の README でも、途中で止めたいときはスクリプトの先頭に set -e を書くよう案内されています[^ssh]。 script: | set -e cd ~/portfolio docker compose pull docker compose up -d docker image prune -f 私のワークフローには入れていません。Actions が成功しているのにサイトが変わらないときは、ここを疑うようにしています。 特定の版に戻す latest のほかにコミットごとの SHA のタグが残るので、VPS で特定の版に戻せます。docker-compose.yml の image を ghcr.io/takibito/portfolio:<戻したいコミットの SHA> に書き換えて、docker compose up -d を実行します。 この状態で次に push しても、docker compose pull は SHA のタグを pull するので、サイトは戻した版のままです。問題を直したコミットを push したあと、image を latest に戻して docker compose pull と docker compose up -d を実行します。ずっと戻しておきたいときは、問題のあるコミットを git revert して push し、正しい版をビルドし直すのが安全です。 さらに安全にするなら - デプロイ専用のユーザーを作って docker グループに入れ、SSH_USER をそのユーザーにする - authorized_keys でデプロイ用の鍵の行頭に from= や command= を付け、鍵の用途を絞る(GitHub Actions の IP の範囲は広いので、from= の運用には手間がかかります) - latest を追うのをやめ、SHA のタグを指定してデプロイする(どの版が動いているかが分かりやすくなります) [^ghcr]: GitHub Docs「Working with the Container registry」 docs.github.com [^visibility]: GitHub Docs「Configuring a package's access control and visibility」 docs.github.com [^compose]: Docker Docs「Define services in Docker Compose」(pull_policy、mem_limit) docs.docker.com [^concurrency]: GitHub Docs「Control workflow concurrency」 docs.github.com [^cache]: Docker Docs「Cache management with GitHub Actions」 docs.docker.com [^ref]: distribution/reference の文法の定義(path-component、alpha-numeric) github.com/distribution/reference/blob/main/refer… [^node20]: GitHub Changelog「Deprecation of Node 20 on GitHub Actions runners」 github.blog [^standalone]: Next.js Docs「output」 nextjs.org [^ssh]: appleboy/ssh-action の README appleboy/ssh-action