Deploying Umbraco
The CMS runs as a systemd service on a Hetzner box, behind Caddy as a reverse proxy with automatic HTTPS. This page covers server access, the build and publish pipeline, and the one-time setup of each component.
For this documentation site’s own deploy, see Deploying the docs site.
Access
Section titled “Access”| Host | 2a01:4ff:f0:8c5f::1 (IPv6) |
| Login | root |
| Provider | Hetzner |
ssh -6 root@2a01:4ff:f0:8c5f::1The -6 is required — the address is IPv6.
Who has access
Section titled “Who has access”| Who | |
|---|---|
| Hetzner account | Mark Drake, Robin Alston |
| SSH key to the server | Mark Drake |
Anyone with Hetzner account access can generate additional SSH keys. Keys are
only needed by engineers who require direct access to the server or the
database — upgrades and routine maintenance. Deploying does not otherwise
require console access beyond the rsync push.
Never commit a private key, and never paste one into these docs. The server address above is fine to record; the key is not.
Routine security updates
Section titled “Routine security updates”Download and apply security updates only:
sudo apt updatesudo apt-get upgrade -o APT::Get::Only-Source=securityA reboot is required if this file exists:
cat /var/run/reboot-requiredIf so:
sudo reboot.NET runtime
Section titled “.NET runtime”Umbraco 17 targets .NET 10.
sudo apt updatesudo apt install -y aspnetcore-runtime-10.0To upgrade later:
sudo apt updatesudo apt install --only-upgrade aspnetcore-runtime-10.0Build and publish
Section titled “Build and publish”1. Build the client bundles first
Section titled “1. Build the client bundles first”cd src/NRI.CMS/Clientnpm run releasenpm run release runs both builds: the browser client into wwwroot/client
and the SSR tree into ../Ssr.
2. Publish from your local machine
Section titled “2. Publish from your local machine”dotnet publish -c Release -r linux-x64 --no-self-contained /p:UseAppHost=false -o ./publish3. Push to the server
Section titled “3. Push to the server”rsync -av --delete -e "ssh -6" \ --exclude="umbraco/" \ ./publish/ "root@[2a01:4ff:f0:8c5f::1]:/var/www/nri-cms/"--exclude="umbraco/" protects the runtime data directories from --delete.
The IPv6 literal has to be bracketed for rsync, and -e "ssh -6" forces IPv6
the same way the plain ssh command does.
4. Restart
Section titled “4. Restart”sudo systemctl restart nri-cmssudo systemctl reload caddyOne-time server setup
Section titled “One-time server setup”Application directory
Section titled “Application directory”sudo mkdir -p /var/www/nri-cmssudo chown www-data:www-data /var/www/nri-cmsThen protect the data folders, which must survive a deploy:
cd /var/www/nri-cmssudo mkdir -p umbraco/Logs umbraco/Datasudo chown -R www-data:www-data umbracosystemd service
Section titled “systemd service”sudo nano /etc/systemd/system/nri-cms.service[Unit]Description=NRI Umbraco CMS (.NET 10)After=network.target
[Service]Type=simpleUser=www-dataWorkingDirectory=/var/www/nri-cmsExecStart=/usr/bin/dotnet NRI.CMS.dll --urls "http://localhost:5000"Restart=alwaysRestartSec=5Environment=ASPNETCORE_ENVIRONMENT=Production
[Install]WantedBy=multi-user.targetEnable and start it:
sudo systemctl daemon-reloadsudo systemctl enable --now nri-cmssudo systemctl status nri-cmsInstall:
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-httpscurl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpgcurl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.listsudo apt updatesudo apt install -y caddysudo systemctl enable --now caddyConfigure:
sudo nano /etc/caddy/Caddyfilenri-inc.org, www.nri-inc.org { reverse_proxy localhost:5000}Reload:
sudo systemctl reload caddyTo upgrade Caddy later:
sudo apt updatesudo apt install --only-upgrade caddysudo systemctl restart caddyOpen questions
Section titled “Open questions”Things this procedure does not yet answer. Worth resolving before the server goes live.
- The other two sites. The Caddyfile above only serves
nri-inc.organdwww.nri-inc.org. Umbraco hosts three top-level sites — NRI Inc, BHPMS, and State Profiles — each resolved by hostname. The other two need their own site blocks (or additional hostnames on this one) or they will not be reachable. - The database. Nothing here covers where the Umbraco database lives, how it is backed up, or how connection strings reach the app in Production.
- Secrets.
appsettings.Production.jsonand any user-secrets equivalents are not part of thersyncpush. How they get onto the server, and who can rotate them, is undefined. - Deploy from a build host. Publishing from a local machine works, but ties
releases to one person’s laptop being correctly set up — in particular to
their having run
npm run releasefirst. A build host or CI job would remove that failure mode.
Source
Section titled “Source”Adapted from Deployment Instructions/Deploy NRI Inc.md in the Compendium
Obsidian vault. Corrections applied here — the ASP.NET Core runtime package,
the nri-inc / nri-cms directory mismatch, the stray /sla path on the
rsync target, and the Caddy header_up placement — should be folded back into
that note so the two do not drift.