Skip to content

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.

Host 2a01:4ff:f0:8c5f::1 (IPv6)
Login root
Provider Hetzner
Terminal window
ssh -6 root@2a01:4ff:f0:8c5f::1

The -6 is required — the address is IPv6.

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.

Download and apply security updates only:

Terminal window
sudo apt update
sudo apt-get upgrade -o APT::Get::Only-Source=security

A reboot is required if this file exists:

Terminal window
cat /var/run/reboot-required

If so:

Terminal window
sudo reboot

Umbraco 17 targets .NET 10.

Terminal window
sudo apt update
sudo apt install -y aspnetcore-runtime-10.0

To upgrade later:

Terminal window
sudo apt update
sudo apt install --only-upgrade aspnetcore-runtime-10.0
Terminal window
cd src/NRI.CMS/Client
npm run release

npm run release runs both builds: the browser client into wwwroot/client and the SSR tree into ../Ssr.

Terminal window
dotnet publish -c Release -r linux-x64 --no-self-contained /p:UseAppHost=false -o ./publish
Terminal window
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.

Terminal window
sudo systemctl restart nri-cms
sudo systemctl reload caddy
Terminal window
sudo mkdir -p /var/www/nri-cms
sudo chown www-data:www-data /var/www/nri-cms

Then protect the data folders, which must survive a deploy:

Terminal window
cd /var/www/nri-cms
sudo mkdir -p umbraco/Logs umbraco/Data
sudo chown -R www-data:www-data umbraco
Terminal window
sudo nano /etc/systemd/system/nri-cms.service
/etc/systemd/system/nri-cms.service
[Unit]
Description=NRI Umbraco CMS (.NET 10)
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/nri-cms
ExecStart=/usr/bin/dotnet NRI.CMS.dll --urls "http://localhost:5000"
Restart=always
RestartSec=5
Environment=ASPNETCORE_ENVIRONMENT=Production
[Install]
WantedBy=multi-user.target

Enable and start it:

Terminal window
sudo systemctl daemon-reload
sudo systemctl enable --now nri-cms
sudo systemctl status nri-cms

Install:

Terminal window
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install -y caddy
sudo systemctl enable --now caddy

Configure:

Terminal window
sudo nano /etc/caddy/Caddyfile
/etc/caddy/Caddyfile
nri-inc.org, www.nri-inc.org {
reverse_proxy localhost:5000
}

Reload:

Terminal window
sudo systemctl reload caddy

To upgrade Caddy later:

Terminal window
sudo apt update
sudo apt install --only-upgrade caddy
sudo systemctl restart caddy

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.org and www.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.json and any user-secrets equivalents are not part of the rsync push. 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 release first. A build host or CI job would remove that failure mode.

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.