Skip to main content

Hosting documentation on a VPS (edox-ops)

GitLab Pages is the default for this repository (see pages-domain.md). Use this runbook when you move the same static build (website/build/) to a VPS you manage with edox-ops — Ubuntu 24.04, Debian 12+, or a Debian derivative (e.g. Raspberry Pi OS).

Pages vs VPS

GitLab PagesVPS + edox-ops
OpsPush master / develop; CI builds and publishesYou run bootstrap, hardening, deploy, TLS on the host
ContentCI artifact website/build/Same tree — rsync from CI download or local make docs
TLSGitLab Let's Encrypt (custom domain)Certbot via edox-ops certs / project certs issue
HardeningGitLab-managed edgehost harden, firewall, fail2ban, nginx templates

The Docusaurus build uses baseUrl: / and a public DOCS_SITE_URL. Rebuild with the production hostname before deploy if the URL changes:

export DOCS_SITE_URL=https://docs.example.com
export DOCS_SITE_BASE_URL=/
python scripts/assemble_docs.py # or download artifact from CI docs:build

Prerequisites

  • Fresh Ubuntu 24.04 or Debian 12+ VPS (SSH as root or sudo)
  • DNS A / AAAA (or CNAME) pointing at the VPS for the docs hostname
  • Python 3.11+ on the VPS with edox-ops installed (pip install edox-ops; see Installation), or deploy from a workstation with SSH + rsync source on the server

Phase 1 — Host baseline

edox-ops bootstrap --yes
edox-ops host harden --yes # security-updates + UFW + fail2ban
edox-ops host logrotate --yes # optional: /var/log/edox-ops/*.log
# edox-ops host ssh-harden --yes # optional; test SSH in a second session first
edox-ops doctor

host harden stores ssh_port in /etc/edox-ops/host.json (default 22). Use --ssh-port if sshd listens elsewhere. Match the port on firewall and fail2ban.

On a dry run first:

edox-ops host harden --yes --dry-run

Phase 2 — TLS tooling

For HTTPS vhosts (project add --tls / project update --tls):

edox-ops certs install --yes
edox-ops certs setup-auto-renew --yes # certbot.timer + nginx reload hook

Issue a certificate after the site is reachable on HTTP (see phase 3–4).

Phase 3 — Register the docs site

Example project slug edox-ops-docs serving https://docs.example.com:

edox-ops project add edox-ops-docs \
--domain docs.example.com \
--root-path /var/www/docs/edox-ops-docs

edox-ops project init edox-ops-docs

Place the static build on the VPS (pick one):

SourceCommand
CI artifactDownload website/build/ from job docs:build/opt/edox-ops-docs-build/
Local buildrsync / scp website/build/ to the VPS
edox-ops project update edox-ops-docs --source /opt/edox-ops-docs-build
edox-ops project deploy edox-ops-docs --yes
edox-ops project enable edox-ops-docs
edox-ops project validate edox-ops-docs

deploy creates a timestamped backup under /var/www/docs/.edox-ops-backups/<slug>/ before rsync --delete. Nginx vhosts include rate limiting, security headers, and static-only methods (GET/HEAD) — no extra CLI step.

Phase 4 — HTTPS

edox-ops project certs issue edox-ops-docs --email you@example.com --yes
edox-ops project update edox-ops-docs --tls
edox-ops project enable edox-ops-docs
edox-ops doctor
curl -fsS https://docs.example.com/docs/intro/
curl -fsS https://docs.example.com/api/index.html

Phase 5 — Prove recovery (once)

edox-ops project backups list edox-ops-docs
# After a test deploy, roll back:
edox-ops project restore edox-ops-docs --yes
edox-ops project validate edox-ops-docs

Ongoing updates

  1. Build docs (docs:build artifact or python scripts/assemble_docs.py locally).
  2. Sync the new website/build/ tree to the VPS source directory.
  3. edox-ops project deploy edox-ops-docs
  4. edox-ops project validate edox-ops-docs and spot-check URLs.

Optional: automate step 1–3 from CI with SSH/rsync (out of scope for edox-ops core; use your own deploy job).

Troubleshooting

SymptomCheck
doctor fails host:firewalledox-ops host firewall --yes; UFW allows SSH/80/443
Certbot HTTP-01 failsSite enabled on port 80; DNS points to this host; /.well-known reachable
404 on /api/Deploy included full website/build/ (with api/ subtree), not Docusaurus-only
Wrong absolute linksRebuild with correct DOCS_SITE_URL before deploy