I wanted to keep Roblox Studio development notes slightly separate from the current nosubject.io site.
Buying a new domain is one option, but starting with a subdomain such as create.nosubject.io is simpler. It lets the new site keep its own structure while still staying under the existing domain.
The main site currently uses Hugo, but I want to try Astro for the subdomain. Astro works well with Markdown and MDX, and it also makes it easy to build pages with components, so it should be a good fit for Roblox Studio screenshots and development logs.
This article uses create.nosubject.io as an example and walks through creating a separate Astro site, then connecting DNS, Nginx, HTTPS, and the GitHub Actions build workflow.
Environment
This article assumes the following environment:
- The existing site is
https://nosubject.io/ - The new site is
https://create.nosubject.io/ - The subdomain site uses Astro
- The production server is Nginx on a Raspberry Pi 5
- Nginx runs through Docker Compose
- Astro is built by a GitHub Actions self-hosted runner
- DNS can be changed from Onamae.com Navi
Astro’s official documentation currently lists Node.js v22.12.0 or higher as a prerequisite. Check the runner on the Raspberry Pi 5 with:
node --version
npm --version
If the result looks like this, Node.js and npm are not installed yet:
-bash: node: command not found
-bash: npm: command not found
On Windows, this message means Node.js is either not installed or not available through PATH:
'node' is not recognized as an internal or external command,
operable program or batch file.
'npm' is not recognized as an internal or external command,
operable program or batch file.
Writing posts and previewing the site locally are assumed to happen on Windows 11. Nginx, HTTPS certificates, and publish directories are assumed to be handled on the Raspberry Pi 5.
Install Node.js
Astro needs Node.js both on the local Windows machine and on the Raspberry Pi 5 where the GitHub Actions self-hosted runner runs.
Astro currently requires Node.js v22.12.0 or higher, so installing the Node.js 24 LTS line is a straightforward choice.
Install On Windows 11
If winget is available on Windows 11, install Node.js LTS with:
winget install --id OpenJS.NodeJS.LTS --exact
After installing it, close and reopen Command Prompt or PowerShell. Existing terminals may not see the updated PATH immediately.
Then check the versions:
node --version
npm --version
When running npm --version in PowerShell, you may see an error saying that npm.ps1 cannot be loaded:
File C:\Program Files\nodejs\npm.ps1 cannot be loaded because running scripts is disabled on this system.
FullyQualifiedErrorId : UnauthorizedAccess
This can happen even when npm --version works in Command Prompt. It does not mean that Node.js failed to install. PowerShell is trying to load npm.ps1, and its execution policy is blocking .ps1 files.
If you only want to check npm immediately, run npm --version in Command Prompt or call npm.cmd from PowerShell:
npm.cmd --version
To use the normal npm command from PowerShell, set RemoteSigned for the current user:
Get-ExecutionPolicy -List
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned
Get-ExecutionPolicy -List
Microsoft describes execution policy as a safety feature that controls the conditions under which PowerShell runs scripts. The CurrentUser scope affects only the current user. RemoteSigned allows locally written scripts to run without a signature, while requiring downloaded scripts to be signed.
After changing the setting, reopen PowerShell and check:
npm --version
In this environment, CurrentUser was initially Undefined.
CurrentUser Undefined
After running Set-ExecutionPolicy, CurrentUser changed to RemoteSigned.
CurrentUser RemoteSigned
After that, the normal npm --version command worked in PowerShell.
11.16.0
If winget is not available, download the LTS Windows Installer from the official Node.js website.
https://nodejs.org/
Install On Raspberry Pi 5
On the Raspberry Pi 5, install Node.js system-wide so the GitHub Actions self-hosted runner can use it easily.
When using the NodeSource Debian/Ubuntu repository, run:
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
After installation, check the versions:
node --version
npm --version
On this environment, the Raspberry Pi 5 showed:
v24.18.0
11.16.0
Check the Windows 11 side in the same way:
node --version
npm --version
On this environment, Windows also showed:
v24.18.0
11.16.0
If the Node.js version is shown as v24.x.x, it satisfies Astro’s requirement.
Limitations
This article assumes that you already manage nosubject.io yourself and can edit the Nginx configuration on the server.
If you use shared hosting, GitHub Pages, Cloudflare Pages, Netlify, Vercel, or another managed hosting service, the DNS and HTTPS screens will be different. The overall idea is the same, but the Nginx server block and Docker Compose examples cannot be used as-is.
DNS propagation time, router port forwarding, IPv4 and IPv6 handling, and Let’s Encrypt certificate issuance can also vary by environment. This article focuses on how the pieces fit together when publishing a new Astro site on a subdomain, rather than covering every provider-specific screen.
Goal
The goal is as follows:
- Keep the existing
nosubject.iosite as it is - Use
create.nosubject.iofor Roblox Studio development notes - Set Astro’s
siteoption to the subdomain - Serve the subdomain from a separate Nginx
serverblock - Build the new site into a separate output directory
Using a subdomain keeps the main blog categories and post list from mixing with the development diary. If the site later moves to its own domain, it is also easier to move as a separate site.
Choose A Directory For The New Site
First, keep the publish directory separate from the main site.
For example, on the Raspberry Pi:
/home/<user>/nosubject-docker/hugo-public
/home/<user>/nosubject-docker/create-public
hugo-public is for the existing nosubject.io site, and create-public is for the new create.nosubject.io site.
Astro builds to dist/ by default. In GitHub Actions, the contents of dist/ will be synced to /home/<user>/nosubject-docker/create-public.
On the Raspberry Pi 5, the directory that must exist is not create-nosubject, the Astro source directory, but create-public, the publish directory that Nginx reads.
mkdir -p /home/<user>/nosubject-docker/create-public
create-nosubject is the Astro project directory created on Windows. When building with GitHub Actions, actions/checkout fetches the repository into the runner’s working directory on the Raspberry Pi 5, so it is fine if there is no create-nosubject directory directly under the home directory.
Only clone the create-nosubject repository on the Raspberry Pi 5 if you also want to test the build manually there.
Create The Astro Site
Create a new repository or working directory for the new Astro site.
npm create astro@latest create-nosubject
cd create-nosubject
If the CLI asks which template to use, a blog template is a good starting point for a development diary.
For this project, I selected:
How would you like to start your new project?
Use blog template
Install dependencies?
Yes
Initialize a new git repository?
Yes
When Initialize a new git repository? is set to Yes, Astro runs git init for the project. After completion, the CLI shows:
Project initialized!
Template copied
Dependencies installed
Git initialized
An npm notice about a new major version may appear at the end. The Astro project has still been created successfully, and it is fine to continue without updating npm immediately.
You can also start from a minimal template and add Markdown lists and tag pages later, but for a development diary, starting with the blog template is faster.
Set the final deployment URL in astro.config.mjs using the site option.
The file generated from the blog template already included @astrojs/mdx, @astrojs/sitemap, and local font settings. Keep those settings and confirm that site is set to https://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()],
});
The actual file may also include font settings using fontProviders.local(). Those settings control the template’s typography and can be left as-is.
import { defineConfig, fontProviders } from 'astro/config';
Astro uses site for things like sitemaps and canonical URLs. Because this site is deployed to a subdomain rather than a subdirectory, do not set base. integrations and fonts have different roles, so do not remove them when adding or checking site.
Add The First Post
If you chose a blog template, add Markdown under src/content/blog/.
src/content/blog/hello-create.md
For example:
---
title: 'Starting Creation Notes'
description: 'Creating a place for Roblox Studio notes'
pubDate: 2026-07-18
tags: ['Roblox Studio', 'Development Log']
---
I will keep notes here about things I try in Roblox Studio.
Preview it locally.
npm run dev -- --host 127.0.0.1 --port 4322
Astro’s development server uses 4321 by default. If that conflicts with another project, specify another port as shown above.
To generate the production static files, run:
npm run build
The build output is generated in dist/ by default.
In this environment, the build completed like this:
[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!
Because /blog/hello-create/index.html was generated, the new post is included in the build. The files generated in dist/ will later be copied to the Nginx publish directory.
Add The DNS Record
Because nosubject.io is managed at Onamae.com, add the DNS record for create.nosubject.io from Onamae.com Navi.
Onamae.com’s official guide, “DNS Record Settings”, shows this path for DNS record settings:
Log in to Onamae.com Navi
-> Name Server/DNS
-> Domain DNS Settings
-> Domain DNS for the target domain
-> DNS Record Settings
-> Add Record
Choose the parent domain nosubject.io, not create.nosubject.io. To add a subdomain, add create as the host name under the parent domain’s DNS settings.
In this setup, nosubject.io already points to the same Raspberry Pi 5 server. A DNS update script also keeps nosubject.io updated when the externally reachable IP address for the Raspberry Pi changes.
For that reason, I did not point create.nosubject.io directly at the server IP address. Instead, I added a CNAME record that points to nosubject.io.
Host name: create
TYPE: CNAME
TTL: 86400
VALUE(TARGET): nosubject.io
In Onamae.com’s screen, enter only create as the host name, not the full create.nosubject.io.
With this setup, when the Raspberry Pi’s public IP address changes, the DNS update script only needs to update nosubject.io. create.nosubject.io will continue to follow it through the CNAME.
If you want to point the subdomain directly to your home server or VPS IP address, use an A record instead of CNAME.
Host name: create
TYPE: A
TTL: 86400
VALUE(TARGET): Your server's IPv4 address
If you also want to specify IPv6 directly, add an AAAA record too.
Host name: create
TYPE: AAAA
TTL: 86400
VALUE(TARGET): Your server's IPv6 address
After entering the record, click the add button, confirm that it appears in the record list, then continue to the confirmation screen and save the change.
If the domain protection application screen appears during the process, choose the option not to enable it if you do not need it. On the final confirmation screen, review the values and apply the change.
Onamae.com’s DNS record settings accept TTL values from 60 to 86400 seconds. If there is no specific reason to change it, 86400, the value used in the official example, is fine.
Check the DNS result.
dig create.nosubject.io
On Windows PowerShell, you can also use:
Resolve-DnsName create.nosubject.io
DNS changes may take some time to propagate. If the name does not resolve immediately after saving, wait a while and check again.
Immediately after saving the setting, I checked from the Raspberry Pi 5 and got 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 means that the name create.nosubject.io does not exist in DNS yet. Because the AUTHORITY SECTION shows the nosubject.io SOA at dnsv.jp, the query is reaching Onamae.com’s authoritative DNS, but the create record is not visible yet.
In that case, check the following:
- The parent domain
nosubject.iois selected in Onamae.com - The host name is
create, notcreate.nosubject.io - The TYPE is
CNAME - The VALUE(TARGET) is
nosubject.io - You clicked through the final confirmation screen and applied the setting
- You waited a while and ran
dig create.nosubject.ioagain
Once the DNS record is visible, the ANSWER SECTION should show a CNAME for create.nosubject.io.
After waiting for a while, the Windows side resolved it like this:
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
This confirms that create.nosubject.io points to nosubject.io, and that nosubject.io then resolves to the current IPv4 address.
Add An Nginx Server Block
In Nginx, create a separate server block for create.nosubject.io.
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’s static output commonly creates index.html files for pages. Using try_files $uri $uri/ /index.html =404; makes regular static pages and client-side routed pages easier to serve.
If Nginx runs through Docker Compose, mount the host-side create-public directory into the container as /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
The key point is to use a different root for the main site and the subdomain. If both server blocks use the same root, both domains will show the same site.
After editing the Nginx configuration, test it.
docker compose exec web nginx -t
If the test passes, reload Nginx.
docker compose exec web nginx -s reload
Create The HTTPS Certificate
The subdomain must be included in a certificate.
If you use Certbot with Nginx integration, add create.nosubject.io like this:
sudo certbot --nginx -d create.nosubject.io
Even if an existing certificate already covers nosubject.io and www.nosubject.io, it will not cover create.nosubject.io unless that name is included.
After issuing the certificate, open:
https://create.nosubject.io/
If Astro’s dist/ files have not been published yet, the page may still show a 404 or empty response. At this stage, the main thing to confirm is that HTTPS opens without a certificate warning.
Use A Separate GitHub Actions Build Destination
If the site is built automatically, sync Astro’s dist/ directory to a publish directory separate from the main site.
Before that, create an empty GitHub repository for create-nosubject.
When creating the repository on GitHub, do not add a README, .gitignore, or license, because the local repository already exists.
Repository name: create-nosubject
Visibility: Public or Private
Add a README file: off
Add .gitignore: None
Choose a license: None
After creating it, add the repository URL as origin and push the local repository.
git remote add origin https://github.com/<owner>/create-nosubject.git
git branch -M main
git push -u origin main
If you want to keep the local branch name as master, skip git branch -M main and push master instead. The workflow below is configured to run on both main and master.
If the GitHub Actions self-hosted runner is registered only for the existing nosubject-hugo repository, it will not automatically be available to the new create-nosubject repository. Register the runner from Settings -> Actions -> Runners in the new repository, or make sure it is available as a shared runner at the organization or account level.
When I tried to run ./config.sh --url https://github.com/usurageha/create-nosubject --token ... inside the existing ~/actions-runner directory on the Raspberry Pi 5, it showed:
Cannot configure the runner because it is already configured.
To reconfigure the runner, run 'config.cmd remove' or './config.sh remove' first.
This means that ~/actions-runner is already configured for another repository. If that existing runner is used by nosubject-hugo, running ./config.sh remove here may stop the existing site’s automated build.
For this setup, keep the existing runner and add another runner directory for create-nosubject.
cd ~
mkdir actions-runner-create
cd actions-runner-create
# Run the curl / tar commands shown in GitHub's runner setup screen.
./config.sh --url https://github.com/usurageha/create-nosubject --token <displayed-token> --name rpi5-create --labels rpi5,create
When the setup asks for a runner group, do not enter rpi5. Just press Enter and use Default.
Enter the name of the runner group to add this runner to: [press Enter for Default]
If you enter rpi5 here, the setup fails like this:
Could not find any self-hosted runner group named "rpi5".
rpi5 is not a runner group. It is a runner label passed with --labels rpi5,create. The workflow’s runs-on selector matches labels.
I ran ./config.sh again, pressed Enter without typing anything at the runner group prompt, and the runner registered successfully:
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.
If you want the runner to stay running as a systemd service, install it as a separate service from this new directory.
sudo ./svc.sh install
sudo ./svc.sh start
sudo ./svc.sh status
If svc.sh status shows active (running), the runner is running as a service.
actions.runner.usurageha-create-nosubject.rpi5-create.service
Active: active (running)
√ Connected to GitHub
Listening for Jobs
Running job: build
On GitHub, rpi5-create also appeared under Settings -> Actions -> Runners.
The workflow uses runs-on: [self-hosted, Linux, ARM64, rpi5], so the new runner needs the rpi5 label. Adding a create label as well makes it easy to narrow the workflow later if needed.
Place a workflow like this in the new Astro site’s repository.
This workflow does not require a pre-existing create-nosubject folder in the Raspberry Pi 5 home directory. The Checkout step fetches the source from GitHub into the runner’s working directory, then npm run build creates dist/, and the publish step syncs it to 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"/
The important part is syncing the contents of dist/ to create-public. To avoid writing the local user name directly in the workflow file, set the publish path as a GitHub Repository variable named ASTRO_DESTINATION.
ASTRO_DESTINATION: /home/<user>/nosubject-docker/create-public
If this points to the existing /home/<user>/nosubject-docker/hugo-public, the new site build may overwrite the main site output.
Check The Published Site
After the build and Nginx configuration are ready, check the site in this order.
curl -I https://create.nosubject.io/
If the response is HTTP/2 200 or HTTP/1.1 200 OK, the top page is being served.
Also check a URL that does not exist.
curl -I https://create.nosubject.io/not-found-test/
If the site has a 404 page, keep the status code as 404 while showing the page generated from Astro’s 404.astro.
Finally, check the Astro URLs.
- The top page opens at
https://create.nosubject.io/ - Post URLs use
https://create.nosubject.io/.../ - Sitemap and canonical URLs point to
create.nosubject.io, notnosubject.io - The main site at
https://nosubject.io/still works as before
After pushing the update to hello-create.md, I was able to open the article at:
https://create.nosubject.io/blog/hello-create/
This confirmed the full flow: pushing to the Astro repository, building with GitHub Actions, syncing to create-public on the Raspberry Pi 5, and serving the site through Nginx.
Register It With Search Tools
Search engines and analytics tools may treat subdomains as separate sites.
If needed, add create.nosubject.io to these services:
- Google Search Console
- Google Analytics
- AdSense
- Bing Webmaster Tools
You do not need to configure everything immediately, but registering the site in Search Console early makes it easier to check indexing later.
Summary
When publishing a separate Astro site on a subdomain such as create.nosubject.io, this order is easy to follow:
- Create a new Astro site
- Set
siteinastro.config.mjstohttps://create.nosubject.io - Add the
createDNS record at Onamae.com - Add an Nginx
serverblock forcreate.nosubject.io - Issue an HTTPS certificate
- Build with
npm run buildin GitHub Actions and syncdist/to the dedicated directory - Check the result with
curland a browser
A subdomain has less friction than buying a new domain. It should be a good first step for a Roblox Studio development diary, and if the site grows later, it can still be moved to its own domain.