Astro サイトをサブドメインで新しく公開する

Astro 自宅サーバ DNS 覚書

Roblox Studio での開発日記を、今の nosubject.io とは少し分けて置きたくなった。

新しいドメインを取る方法もあるが、まずは create.nosubject.io のようなサブドメインを作り、別の静的サイトとして公開するのが扱いやすそうだ。

このサイト本体は Hugo で運用しているが、サブドメイン側では Astro を使ってみる。Astro は Markdown や MDX を扱いやすく、コンポーネントを使ったページ作りにも寄せやすいので、Roblox Studio のスクリーンショットや制作ログを置く場所として試しやすい。

この記事では、create.nosubject.io を例にして、Astro サイトを新しく作り、DNS、Nginx、HTTPS、GitHub Actions のビルド設定までつなげる流れをまとめる。

実施した環境

この記事では、次のような環境を前提にしている。

  • 既存サイトは https://nosubject.io/
  • 新しいサイトは https://create.nosubject.io/
  • サブドメイン側の静的サイトジェネレーターは Astro
  • 公開先は Raspberry Pi 5 上の Nginx
  • Nginx は Docker Compose で起動
  • Astro のビルドは GitHub Actions の self-hosted runner で実行
  • DNS はお名前.com Navi で変更できる状態

Astro の公式ドキュメントでは、現在の前提として Node.js v22.12.0 以上が案内されている。Raspberry Pi 5 の runner 側でも、node --version で条件を満たしているか確認しておく。

node --version
npm --version

この時点で次のように表示される場合は、まだ Node.js と npm が入っていない。

-bash: node: command not found
-bash: npm: command not found

Windows 側でも、次のように表示される場合は Node.js が入っていないか、PATH に通っていない。

'node' は、内部コマンドまたは外部コマンド、操作可能なプログラムまたはバッチ ファイルとして認識されていません。
'npm' は、内部コマンドまたは外部コマンド、操作可能なプログラムまたはバッチ ファイルとして認識されていません。

ローカルで記事を書いたり表示確認したりする作業は Windows 11 側で行い、本番公開に関わる Nginx、証明書、公開ディレクトリの作業は Raspberry Pi 5 側で行う想定。

Node.js をインストールする

Astro を使うには、ローカルの Windows 側にも、GitHub Actions の self-hosted runner が動く Raspberry Pi 5 側にも Node.js が必要になる。

Astro の現在の要件は Node.js v22.12.0 以上なので、LTS 版の Node.js 24 系を入れておくのが分かりやすい。

Windows 11 に入れる

Windows 11 では、winget が使えるなら次のコマンドで Node.js LTS を入れられる。

winget install --id OpenJS.NodeJS.LTS --exact

インストール後、開いているコマンドプロンプトや PowerShell をいったん閉じて、開き直す。PATH が更新されるまで、既存のターミナルでは nodenpm が見つからないことがある。

開き直したら確認する。

node --version
npm --version

PowerShell で npm --version を実行したとき、次のように npm.ps1 が読み込めないエラーになることがある。

このシステムではスクリプトの実行が無効になっているため、ファイル C:\Program Files\nodejs\npm.ps1 を読み込むことができません。
FullyQualifiedErrorId : UnauthorizedAccess

コマンドプロンプトでは npm --version が実行できるのに、PowerShell だけでこのエラーになる場合がある。これは Node.js のインストール失敗ではなく、PowerShell が npm.ps1 を読み込もうとして、実行ポリシーで .ps1 ファイルが止められている状態。

すぐ確認したいだけなら、コマンドプロンプトで npm --version を実行するか、PowerShell から npm.cmd を呼ぶ。

npm.cmd --version

PowerShell から通常の npm コマンドを使えるようにするなら、現在のユーザーだけ RemoteSigned にする。

Get-ExecutionPolicy -List
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned
Get-ExecutionPolicy -List

Microsoft の説明では、PowerShell の実行ポリシーはスクリプトを実行する条件を制御する安全機能で、CurrentUser スコープは現在のユーザーだけに適用される。RemoteSigned はローカルで作成したスクリプトは署名なしで実行でき、インターネットから取得したスクリプトには署名を要求する設定。

設定後、PowerShell を開き直して確認する。

npm --version

この環境では、最初は CurrentUserUndefined だった。

CurrentUser       Undefined

Set-ExecutionPolicy の実行後は、CurrentUserRemoteSigned になった。

CurrentUser    RemoteSigned

その後、PowerShell でも通常の npm --version が通るようになった。

11.16.0

winget が使えない場合は、Node.js 公式サイトから LTS の Windows Installer をダウンロードしてインストールする。

https://nodejs.org/

Raspberry Pi 5 に入れる

Raspberry Pi 5 側では、GitHub Actions の self-hosted runner からも使いやすいように、システム全体に Node.js を入れる。

NodeSource の Debian/Ubuntu 向けリポジトリを使う場合は、次のようにする。

sudo apt update
sudo apt install -y curl ca-certificates
curl -fsSL https://deb.nodesource.com/setup_24.x -o nodesource_setup.sh
sudo -E bash nodesource_setup.sh
sudo apt install -y nodejs

インストールできたら確認する。

node --version
npm --version

この環境では、Raspberry Pi 5 側で次のように表示された。

v24.18.0
11.16.0

Windows 11 側でも、同じように確認する。

node --version
npm --version

この環境では、Windows 側でも次のように表示された。

v24.18.0
11.16.0

v24.x.x のように表示されれば、Astro の要件は満たしている。

制限事項

この記事は、すでに nosubject.io を自分で管理していて、サーバ側の Nginx 設定も変更できる前提で書いている。

そのため、レンタルサーバや GitHub Pages、Cloudflare Pages、Netlify、Vercel などを使う場合は、DNS や HTTPS の設定画面が変わる。考え方は同じだが、Nginx の server ブロックや Docker Compose の設定はそのまま使えない。

また、DNS の反映時間、ルーターのポート開放、IPv4 と IPv6 の扱い、Let’s Encrypt の証明書発行方法は環境によって差が出る。この記事では、細かい管理画面の操作ではなく、サブドメインを新しい Astro サイトとして公開するために必要な設定のつながりを中心に整理する。

やりたいこと

今回の目的は次の通り。

  • 既存の nosubject.io はそのまま残す
  • create.nosubject.io を Roblox Studio 開発日記用のサイトにする
  • Astro の設定では site をサブドメインにする
  • Nginx ではメインサイトとは別の server ブロックで配信する
  • GitHub Actions などの自動ビルド先も別ディレクトリにする

サブドメインにしておくと、メインブログの記事一覧やカテゴリを混ぜずに済む。あとから独自ドメインへ移す場合も、サイト単位で移動しやすい。

サイト用のディレクトリを決める

まず、公開先のディレクトリをメインサイトと分ける。

たとえば Raspberry Pi 上で次のように分ける。

/home/<user>/nosubject-docker/hugo-public
/home/<user>/nosubject-docker/create-public

hugo-public は既存の nosubject.io 用、create-public は新しい create.nosubject.io 用にする。

Astro のビルド結果は、標準ではプロジェクト内の dist/ に出力される。GitHub Actions では、この dist/ の中身を /home/<user>/nosubject-docker/create-public へ同期する。

ここで Raspberry Pi 5 側に必要なのは、Astro のソースディレクトリである create-nosubject ではなく、Nginx が読む公開先ディレクトリの create-public

mkdir -p /home/<user>/nosubject-docker/create-public

create-nosubject は Windows 側で作った Astro プロジェクト名。GitHub Actions でビルドする場合、Raspberry Pi 5 側では runner の作業ディレクトリに actions/checkout がリポジトリを取得するので、ホームディレクトリ直下に create-nosubject がなくても問題ない。

Raspberry Pi 5 上でも手動でビルド確認したい場合だけ、別途 GitHub から create-nosubject リポジトリを clone する。

Astro サイトを作る

新しいリポジトリ、または新しい作業ディレクトリで Astro サイトを作る。

npm create astro@latest create-nosubject
cd create-nosubject

対話形式でテンプレートを聞かれたら、開発日記用には blog 系のテンプレートを選ぶと始めやすい。

今回の入力は次のようにした。

How would you like to start your new project?
  Use blog template

Install dependencies?
  Yes

Initialize a new git repository?
  Yes

Initialize a new git repository?Yes を選んだ場合は、Astro 側で git init まで実行される。完了すると次のように表示される。

Project initialized!
Template copied
Dependencies installed
Git initialized

最後に npm の新しいメジャーバージョンがあるという notice が出ることがあるが、Astro プロジェクトの作成自体は完了している。すぐに npm を更新しなくても、このまま次の作業へ進める。

最小構成で作った場合でも、あとから Markdown の記事一覧やタグページを足せる。ただ、制作日記として始めるなら、最初から blog template を選ぶ方が手早い。

Astro では、公開先のURLを astro.config.mjssite に設定する。

blog template で作成した直後の astro.config.mjs には、@astrojs/mdx@astrojs/sitemap、ローカルフォント設定が入っていた。これらは消さずに、sitehttps://create.nosubject.io になっていることを確認する。

// @ts-check

import mdx from '@astrojs/mdx';
import sitemap from '@astrojs/sitemap';
import { defineConfig } from 'astro/config';

export default defineConfig({
  site: 'https://create.nosubject.io',
  integrations: [mdx(), sitemap()],
});

実際のファイルでは、さらに fontProviders.local() を使ったフォント設定も入っている。そこはテンプレートの見た目に関わる設定なので、そのまま残してよい。

import { defineConfig, fontProviders } from 'astro/config';

Astro の site は、sitemap や canonical URL などで使われる。サブディレクトリではなくサブドメインで公開するので、base は指定しない。integrationsfonts は別の役割なので、site を追加・確認するときに削除しない。

最初の記事を作る

blog テンプレートを選んだ場合は、src/content/blog/ の下に Markdown を追加する。

src/content/blog/hello-create.md

例として、次のような記事を置く。

---
title: '制作日記を始める'
description: 'Roblox Studio で試したことを残す場所を作る'
pubDate: 2026-07-18
tags: ['Roblox Studio', '開発日記']
---

Roblox Studio で試したことを、ここに残していく。

ローカルで確認する。

npm run dev -- --host 127.0.0.1 --port 4322

Astro の開発サーバーは標準で 4321 を使う。既存作業とぶつかる場合は、上のように別ポートを指定する。

本番用の静的ファイルを生成するには、次を実行する。

npm run build

ビルド結果は標準では dist/ に生成される。

この環境では、次のようにビルドできた。

[build] output: "static"
[build] directory: C:\Users\kkitta\GitHub\create-nosubject\dist\
├─ /blog/hello-create/index.html
├─ /rss.xml
├─ /index.html
[@astrojs/sitemap] `sitemap-index.xml` created at `dist`
[build] 9 page(s) built in 1.16s
[build] Complete!

/blog/hello-create/index.html が生成されているので、追加した記事もビルド対象に入っている。dist/ にできたファイル一式を、あとで Nginx の公開ディレクトリへ配置する。

DNS にサブドメインを追加する

nosubject.io はお名前.comで管理しているので、お名前.com Navi から create.nosubject.io のレコードを追加する。

お名前.comの公式ガイド「DNS関連機能の設定:DNSレコード設定」では、DNSレコード設定は次の流れで開く。

お名前.com Navi にログイン
  -> ネームサーバー/DNS
  -> ドメインDNS設定
  -> 対象ドメインの「ドメインDNS」
  -> DNSレコード設定
  -> レコード追加

対象ドメインは create.nosubject.io ではなく、親ドメインの nosubject.io を選ぶ。サブドメインのDNSレコードを追加する場合も、親ドメイン側のDNS設定に create というホスト名を追加する。

今回は、nosubject.io がすでに同じ Raspberry Pi 5 のサーバを向いている。さらに DNS 更新スクリプトで、外部から Raspberry Pi を参照できる IP アドレスが変わっても nosubject.io 側が更新されるようにしている。

そのため、create.nosubject.io にはサーバのIPアドレスを直接入れず、nosubject.io を参照する CNAME レコードを追加した。

ホスト名: create
TYPE: CNAME
TTL: 86400
VALUE(TARGET): nosubject.io

お名前.comの画面では、完全な create.nosubject.io ではなく、ホスト名に create だけを入力する。公式ガイドでも、xxx.domain.com の場合は xxx のみを入力する形で説明されている。

この形にしておくと、Raspberry Pi 側のIPアドレスが変わっても、DNS更新スクリプトが nosubject.io を更新すれば create.nosubject.io も同じ向き先を参照できる。

自宅サーバや VPS の IP アドレスへ直接向ける場合は、CNAME ではなく A レコードを使う。

ホスト名: create
TYPE: A
TTL: 86400
VALUE(TARGET): サーバのIPv4アドレス

IPv6 も直接指定する場合は AAAA レコードも追加する。

ホスト名: create
TYPE: AAAA
TTL: 86400
VALUE(TARGET): サーバのIPv6アドレス

入力したら、画面上の「追加」ボタンを押す。レコード一覧に追加されたことを確認してから、確認画面へ進んで設定を保存する。

途中でドメインプロテクションの申込み画面が表示された場合は、必要なければ「設定しない」を選んで進む。最後に確認画面で入力内容を確認し、「設定する」を押して完了させる。

お名前.comのDNSレコード設定では、TTL は 60 から 86400 秒の範囲で入力できる。特に理由がなければ、公式ガイドの入力例と同じ 86400 でよい。

反映を確認する。

dig create.nosubject.io

Windows の PowerShell なら次でも確認できる。

Resolve-DnsName create.nosubject.io

DNSの反映には時間がかかることがある。設定直後に引けなくても、レコード内容が正しければしばらく待ってから再確認する。

設定直後に Raspberry Pi 5 で確認したところ、次のように NXDOMAIN になった。

status: NXDOMAIN
ANSWER: 0

QUESTION SECTION:
create.nosubject.io.           IN      A

AUTHORITY SECTION:
nosubject.io.           300     IN      SOA     01.dnsv.jp. hostmaster.dnsv.jp.

NXDOMAIN は、create.nosubject.io という名前がまだDNS上に存在しないという意味。AUTHORITY SECTIONnosubject.io の SOA として dnsv.jp が出ているので、お名前.comの権威DNSまでは問い合わせできているが、create のレコードがまだ見えていない状態と読める。

この場合は、次の点を確認する。

  • お名前.comで親ドメイン nosubject.io を選んでいるか
  • ホスト名に create.nosubject.io ではなく create だけを入れているか
  • TYPE が CNAME になっているか
  • VALUE(TARGET) が nosubject.io になっているか
  • 「追加」だけで終わらず、最後の確認画面で「設定する」まで押したか
  • しばらく待ってから再度 dig create.nosubject.io を実行したか

DNSレコードが反映されると、ANSWER SECTIONcreate.nosubject.io の CNAME が表示されるようになる。

しばらく待ってから Windows 側で確認すると、次のように引けるようになった。

Name                           Type   TTL   Section    NameHost
----                           ----   ---   -------    --------
create.nosubject.io            CNAME  3600  Answer     nosubject.io

Name       : nosubject.io
QueryType  : A
TTL        : 3600
Section    : Answer
IP4Address : 60.84.210.40

これで create.nosubject.ionosubject.io を参照し、その先で現在のIPv4アドレスへ解決できていることが確認できた。

Nginx にサブドメイン用の設定を追加する

Nginx では、既存の nosubject.io 用とは別に create.nosubject.io 用の server ブロックを作る。

server {
    listen 80;
    listen [::]:80;

    server_name create.nosubject.io;

    root /var/www/create;
    index index.html;

    location / {
        try_files $uri $uri/ /index.html =404;
    }

    error_page 404 /404.html;

    location = /404.html {
        internal;
    }
}

Astro の静的出力では、ページごとに index.html が生成される構成が多い。try_files $uri $uri/ /index.html =404; としておくと、通常の静的ページと、クライアント側でルーティングするページの両方を扱いやすい。

Docker Compose で Nginx を動かしている場合は、ホスト側の create-public をコンテナ内の /var/www/create へマウントする。

services:
  web:
    image: nginx:stable
    volumes:
      - ./hugo-public:/var/www/html:ro
      - ./create-public:/var/www/create:ro
      - ./nginx/conf.d:/etc/nginx/conf.d:ro

メインサイトとサブドメインで root を分けるのが大事。ここを同じにすると、どちらのドメインでも同じサイトが表示される。

設定を書いたら構文を確認する。

docker compose exec web nginx -t

問題なければ Nginx を reload する。

docker compose exec web nginx -s reload

HTTPS 証明書を用意する

サブドメインは、メインドメインとは別名として証明書に含める必要がある。

Certbot を Nginx 連携で使っているなら、次のように create.nosubject.io を追加する。

sudo certbot --nginx -d create.nosubject.io

既存の証明書に nosubject.iowww.nosubject.io をまとめている場合でも、create.nosubject.io が証明書に入っていなければ HTTPS では使えない。

証明書を発行したら、ブラウザで次を開く。

https://create.nosubject.io/

まだ Astro の dist/ を配置していない場合は 404 や空の表示になることがある。Nginx と証明書の段階では、まず HTTPS の警告が出ないところまで確認できればよい。

GitHub Actions のビルド先を分ける

自動ビルドする場合は、Astro の dist/ をメインサイトとは別の公開先へ同期する。

その前に、GitHub 側に create-nosubject 用の空リポジトリを作っておく。

GitHub の画面で新しいリポジトリを作るときは、既存のローカルリポジトリを push するので、README、.gitignore、license は追加しない。

Repository name: create-nosubject
Visibility: 好みに応じて Public または Private
Add a README file: off
Add .gitignore: None
Choose a license: None

作成後、表示されたリポジトリURLを remote として追加する。

git remote add origin https://github.com/<owner>/create-nosubject.git
git branch -M main
git push -u origin main

すでにローカルブランチ名を master のまま使う場合は、git branch -M main は実行せずに master を push してもよい。今回の workflow は mainmaster の両方で動くようにしておく。

また、GitHub Actions の self-hosted runner が既存の nosubject-hugo リポジトリ専用で登録されている場合、新しい create-nosubject リポジトリではそのまま使えない。新しいリポジトリの Settings -> Actions -> Runners で runner を登録するか、組織/アカウント側で共有 runner として使える状態にしておく。

Raspberry Pi 5 で既存の ~/actions-runner に対して ./config.sh --url https://github.com/usurageha/create-nosubject --token ... を実行すると、次のように表示された。

Cannot configure the runner because it is already configured.
To reconfigure the runner, run 'config.cmd remove' or './config.sh remove' first.

これは、~/actions-runner がすでに別リポジトリ用に設定済みという意味。既存の runner が nosubject-hugo のビルドで使われている場合、ここで ./config.sh remove を実行すると既存サイトの自動ビルドが止まる可能性がある。

今回は既存 runner を消さず、別ディレクトリに create-nosubject 用の runner を追加する。

cd ~
mkdir actions-runner-create
cd actions-runner-create

# GitHub の runner 追加画面に表示された curl / tar コマンドを実行する

./config.sh --url https://github.com/usurageha/create-nosubject --token <表示されたtoken> --name rpi5-create --labels rpi5,create

登録中に runner group を聞かれたら、rpi5 とは入力せず、そのまま Enter を押して Default にする。

Enter the name of the runner group to add this runner to: [press Enter for Default]

ここで rpi5 と入力すると、次のように失敗する。

Could not find any self-hosted runner group named "rpi5".

rpi5 は runner group ではなく、--labels rpi5,create で付ける runner label。workflow の runs-on は label を見て runner を選ぶ。

もう一度 ./config.sh を実行し、runner group の質問で何も入力せず Enter を押すと、次のように登録できた。

Enter the name of the runner group to add this runner to: [press Enter for Default]

√ Runner successfully added

Enter name of work folder: [press Enter for _work]

√ Settings Saved.

runner を systemd サービスとして常駐させる場合も、既存 runner とは別のサービスとして入れる。

sudo ./svc.sh install
sudo ./svc.sh start
sudo ./svc.sh status

svc.sh status で次のように active (running) になれば、サービスとして起動できている。

actions.runner.usurageha-create-nosubject.rpi5-create.service
Active: active (running)
√ Connected to GitHub
Listening for Jobs
Running job: build

GitHub 側の Settings -> Actions -> Runners でも、rpi5-create が追加されたことを確認できた。

workflow 側は runs-on: [self-hosted, Linux, ARM64, rpi5] なので、追加 runner に rpi5 ラベルが付いていれば拾われる。create ラベルも付けておくと、あとで runs-on をより限定したいときに使える。

新しい Astro サイトのリポジトリには、次のような workflow を置く。

この workflow では、Raspberry Pi 5 のホームディレクトリに create-nosubject フォルダをあらかじめ作っておく必要はない。Checkout step が GitHub からソースを runner の作業ディレクトリへ取得し、npm run build でできた dist/ASTRO_DESTINATION へ同期する。

name: Build Create Astro site

on:
  push:
    branches:
      - main
      - master
  workflow_dispatch:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

permissions:
  contents: read

jobs:
  build:
    runs-on: [self-hosted, Linux, ARM64, rpi5]
    env:
      ASTRO_DESTINATION: ${{ vars.ASTRO_DESTINATION }}
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 24
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Build
        run: npm run build

      - name: Check publish destination
        run: |
          test -n "$ASTRO_DESTINATION"
          case "$ASTRO_DESTINATION" in
            *create-public|*create-public/) ;;
            *) echo "ASTRO_DESTINATION must point to the create-public directory"; exit 1 ;;
          esac
          test -d "$ASTRO_DESTINATION"
          command -v rsync

      - name: Publish
        run: rsync -a --delete dist/ "$ASTRO_DESTINATION"/

ポイントは、dist/ の中身を create-public へ同期すること。workflow ファイルにローカルユーザー名を直接書かないように、公開先は GitHub の Repository variables に ASTRO_DESTINATION として設定する。

ASTRO_DESTINATION: /home/<user>/nosubject-docker/create-public

ここを既存サイトと同じ /home/<user>/nosubject-docker/hugo-public にしてしまうと、メインサイトの出力を上書きしてしまう。

公開後に確認する

ビルドと Nginx の設定が終わったら、次の順番で確認する。

curl -I https://create.nosubject.io/

HTTP/2 200HTTP/1.1 200 OK が返ればトップページは表示できている。

存在しない URL も確認する。

curl -I https://create.nosubject.io/not-found-test/

404 ページを用意している場合は、ステータスコードが 404 のまま、本文として Astro の 404.astro から生成されたページが表示される状態にする。

最後に、Astro 側の URL も確認する。

  • トップページが https://create.nosubject.io/ で開ける
  • 記事ページの URL が https://create.nosubject.io/.../ になる
  • sitemap や canonical URL が nosubject.io ではなく create.nosubject.io になる
  • メインサイト https://nosubject.io/ は今まで通り表示される

更新テスト用に hello-create.md を push したあと、次のURLで記事を表示できた。

https://create.nosubject.io/blog/hello-create/

これで、Astro リポジトリへの push、GitHub Actions でのビルド、Raspberry Pi 5 上の create-public への同期、Nginx からの配信まで一通り動いたことを確認できた。

Search Console などに登録する

サブドメインは、検索エンジンや解析ツールから見ると別サイトとして扱うことがある。

必要であれば、次のサービスにも create.nosubject.io を追加する。

  • Google Search Console
  • Google Analytics
  • AdSense
  • Bing Webmaster Tools

最初から全部を設定しなくても、記事を増やしていくなら Search Console だけは早めに登録しておくと、インデックス状況を確認しやすい。

まとめ

create.nosubject.io のようなサブドメインで Astro サイトを分ける場合、作業は次の順番で進めると分かりやすい。

  1. 新しい Astro サイトを作る
  2. astro.config.mjssitehttps://create.nosubject.io にする
  3. お名前.comで create のDNSレコードを追加する
  4. Nginx に create.nosubject.io 用の server ブロックを追加する
  5. HTTPS 証明書を発行する
  6. GitHub Actions で npm run build し、dist/ を専用ディレクトリへ同期する
  7. curl とブラウザで表示を確認する

新しいドメインを取るより、サブドメインの方が始めるまでの摩擦が少ない。Roblox Studio の開発日記として続けてみて、記事が増えてから独自ドメイン化する、という流れでもよさそうだ。