01 / Architecture
Start by separating four responsibilities
Build
A static site may generate HTML/CSS/JS into an output directory; a Node application needs code, dependencies and an entrypoint that stays running.
Local service
Static files need a file server. A Node app should listen on a controlled address and port, preferably loopback when Caddy is the only intended public entry point.
Public boundary
Caddy receives the domain, terminates HTTPS and either serves files or sends the request to the Node process. An administration panel should not share that public surface by default.
Update path
GitHub can be the source of code and a self-hosted runner can execute builds, tests and deployment, but replacing a revision is a different responsibility from keeping the service alive.
02 / Caddy
Caddy: static files and reverse proxy are different cases
For a static site, Caddy can serve an output directory directly with file_server. For Node, the usual pattern is to keep the application on a local port and put Caddy in front with reverse_proxy. When the config uses a public hostname and DNS/ports are ready, Caddy can manage HTTPS automatically.
- For static hosting, expose only the build output directory; do not publish the repository, .git, .env files or working directories.
- For Node, listen on loopback when only Caddy should communicate with the application. The internal port does not need router exposure.
- The public domain must resolve to the right IP and TCP 80/443 must reach Caddy for normal public certificate automation.
- Validate configuration before reloading it and check the real public hostname after the change.
- A 200 from the local backend and a 200 from the public hostname prove different parts of the chain; you need both.
03 / GitHub
GitHub is the revision source, not the process supervisor
A workflow on a self-hosted runner can check out code, install dependencies, run tests, build output and prepare a new revision on the same server. That does not turn GitHub Actions into a process manager. The static or Node service still needs its own execution, restart or controlled replacement model, and deployment should know exactly which revision is active.
Deployment evidence worth retaining
- Commit or SHA being deployed and its source branch.
- Build and test result before touching the active service.
- Prepared directory or revision outside the current publication.
- Atomic switch or an explicit activation/restart step for Node.
- Local smoke check followed by a public HTTPS smoke check.
- Previous revision available for rollback when activation fails.
04 / Real case
Real case: NRS Home Server
NRS Home Server applies this separation on Windows. Deployments retain GitHub provenance and history; StaticHost and AppHost keep services independent from the dashboard; and Caddy publication is opt-in per domain. Caddy remains external: NRS Home Server does not install it or change DNS, router, firewall or system certificate stores.
- Static sites are registered and served through an independent StaticHost; Node applications use reserved loopback ports.
- A public domain can target only registered services. The dashboard, Drive, backups and Minecraft administration do not become public targets.
- Generated Caddy configuration is validated before reload. A prepared gateway is not described as Internet-reachable when DNS or external networking has not been verified.
- GitHub-backed projects retain the deployed revision, manual freshness checking and a compatible previous revision for recovery.
- The lightweight persistence model starts after Windows sign-in; it is not equivalent to pre-login 24/7 hosting.
05 / Checklist
Publishing checklist
- 01
Decide whether the project is static or needs a persistent Node process.
- 02
Keep the Node app on loopback and expose only Caddy to the Internet.
- 03
Verify DNS, ports 80/443 and firewall before expecting public HTTPS.
- 04
Run build and QA against the exact revision you intend to deploy.
- 05
Prepare the change away from the active service and retain a rollback path.
- 06
Check the local service first, then the real public HTTPS hostname.
- 07
Do not let untrusted workflow code execute with server privileges.
06 / Boundaries
What this architecture does not solve by itself
Caddy does not replace backups, monitoring, process management, operating-system updates or a security policy. A self-hosted runner is not a sandbox either: workflows execute on a machine you control and state can persist between jobs. A critical server needs more operational discipline than a home PC that is only powered on when required.