Skip to main content

Deploy the Docs Site

docs.yatcogroup.com is hosted on the same AWS EC2 instance that runs YGC Odoo production (m8g.xlarge, ap-southeast-1, SSH port 827). It serves a Docusaurus static site plus an Express-based Claude chat backend (see Search & Chatbot).

This runbook covers everything needed to bring the site up on a fresh host. For day-to-day deploys, jump to Subsequent deploys.

Topology

┌──────────────────────────────────────────────────────────┐
│ AWS EC2 (Ubuntu 24.04, ARM64) │
│ │
│ nginx :443 ─┬──> /var/www/ygc-docs/site │ Docusaurus build
│ └──> 127.0.0.1:3940 /api/* proxy │
│ │
│ ygc-docs-chat.service (systemd, user=ubuntu) │
│ ├──> /opt/ygc-docs/server Express + tsx, port 3940 │
│ ├──> /opt/ygc-docs/docs markdown loaded at start │
│ └──> /etc/ygc-docs-chat.env ANTHROPIC_API_KEY (600) │
└──────────────────────────────────────────────────────────┘

The TLS cert is the shared yatcogroup-multi Let's Encrypt cert at /etc/letsencrypt/live/yatcogroup-multi/, also used by the Odoo subdomains. New domains are added to it with certbot --expand.

Prerequisites

  • DNS: docs.yatcogroup.com resolves to the EC2 public IP.
  • SSH on port 827 as ubuntu with key auth and passwordless sudo (cloud-init default).
  • Node.js 20 LTS on the host (Ubuntu 24.04 ships 18, which is EOL and too old for glob ^11).
  • nginx already installed; the wildcard *.yatcogroup.com :80 vhost from /etc/nginx/sites-available/odoo17 provides the HTTP→HTTPS redirect, so this runbook only adds the :443 vhost for docs.

1. Install Node 20 LTS

curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
node --version # v20.x.x

2. Create the directory layout

sudo install -d -o ubuntu -g ubuntu -m 755 \
/var/www/ygc-docs \
/var/www/ygc-docs/site \
/opt/ygc-docs \
/opt/ygc-docs/docs \
/opt/ygc-docs/server
PathOwnerPurpose
/var/www/ygc-docs/siteubuntu:ubuntuDocusaurus build output, served by nginx
/opt/ygc-docs/docsubuntu:ubuntuSource markdown — chat server reads at startup
/opt/ygc-docs/serverubuntu:ubuntuChat server code + node_modules

ubuntu owns everything so rsync deploy from a laptop works without sudo. nginx (running as www-data) reads /var/www/ygc-docs/site via the directories' o+rx permission.

The chat server resolves its corpus path as ../docs relative to its source file, so server and docs must be siblings under the same parent.

3. Drop in the chat server's API key

sudo install -o root -g root -m 600 /dev/null /etc/ygc-docs-chat.env
sudo $EDITOR /etc/ygc-docs-chat.env
# Add one line:
# ANTHROPIC_API_KEY=sk-ant-...

Mode 600 root-owned: only systemd (PID 1) reads it; the value is exposed to the chat process via EnvironmentFile= at service start.

4. systemd unit

sudo cp ygc17-docs/scripts/ygc-docs-chat.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable ygc-docs-chat.service

Don't start it yet — the node_modules directory doesn't exist until the first deploy (step 6).

5. nginx vhost + TLS

Copy the vhost from the repo:

sudo cp ygc17-docs/scripts/docs.yatcogroup.com-nginx.conf \
/etc/nginx/sites-available/docs.yatcogroup.com
sudo ln -s /etc/nginx/sites-available/docs.yatcogroup.com /etc/nginx/sites-enabled/
sudo nginx -t

If the cert doesn't yet cover docs.yatcogroup.com, expand the shared cert. List the existing SANs first so you don't drop any:

openssl x509 -in /etc/letsencrypt/live/yatcogroup-multi/cert.pem \
-noout -ext subjectAltName | grep -oE 'DNS:[a-z.]+'

Then re-issue with the existing list plus -d docs.yatcogroup.com:

sudo certbot certonly --nginx --cert-name yatcogroup-multi --expand --non-interactive \
-d <every-existing-SAN> \
-d docs.yatcogroup.com
sudo systemctl reload nginx

Renewal is automatic (certbot.timer). The deploy hook at /etc/letsencrypt/renewal-hooks/deploy/reload-nginx reloads nginx after each renewal.

6. First deploy

The deploy script handles the build, the rsync of static + corpus, and the chat-server restart. It does not install node_modules — the first deploy needs one extra step:

# From your laptop, in the docs site root
cd /Users/jsalinga/odoo/odoo17/custom/ygc17/ygc17-docs

# Sync the chat server source so node_modules has somewhere to land
rsync -az --exclude=node_modules --exclude=.env -e "ssh -p 827" \
server/ ubuntu@docs.yatcogroup.com:/opt/ygc-docs/server/

# Install deps on the host (need devDeps because tsx is in devDependencies)
ssh -p 827 ubuntu@docs.yatcogroup.com 'cd /opt/ygc-docs/server && npm install'

# Now the unit can start
ssh -p 827 ubuntu@docs.yatcogroup.com 'sudo systemctl start ygc-docs-chat'

# Run the normal deploy
./scripts/deploy.sh

Verify:

curl -I https://docs.yatcogroup.com/ # HTTP/2 200
curl https://docs.yatcogroup.com/api/health # {"status":"ok","docsSize":...}

Subsequent deploys

cd /Users/jsalinga/odoo/odoo17/custom/ygc17/ygc17-docs
./scripts/deploy.sh

The script:

  1. Runs npm run build locally.
  2. rsyncs build/ubuntu@docs.yatcogroup.com:/var/www/ygc-docs/site/.
  3. rsyncs docs/ubuntu@docs.yatcogroup.com:/opt/ygc-docs/docs/ (the chat corpus).
  4. sudo systemctl restart ygc-docs-chat so the chat server reloads the docs.

The --delete rsync flag ensures removed pages don't linger.

Troubleshooting

SymptomLikely causeFix
Host key verification failed on first deployLaptop hasn't seen this hostname yetssh-keyscan -p 827 docs.yatcogroup.com >> ~/.ssh/known_hosts
Permission denied (publickey)SSH key not in authorized_keys for ubuntuAdd your pubkey on the server
404 on every page after deployWrong root or try_files chainCheck the location / block in the nginx vhost — should fall back to /404.html
Mixed-content warningsLoaded over HTTPThe wildcard *.yatcogroup.com :80 vhost should permanent-redirect to :443; confirm /etc/nginx/sites-enabled/odoo17 is enabled
502 Bad Gateway on /api/*Chat server not runningsudo systemctl status ygc-docs-chat, sudo journalctl -u ygc-docs-chat -n 50
Chat answers with no docs context (docsSize: 0)server and docs directories not siblingsMove docs/ to be a sibling of server/ under /opt/ygc-docs/
Cert renewal succeeds but nginx serves old certnginx didn't reload after renewalVerify /etc/letsencrypt/renewal-hooks/deploy/reload-nginx exists and is executable
403 Forbidden on a static pageDoc-root owner/perm issueRe-run the install -d from step 2; nginx needs o+rx on every parent dir
Sudo password prompt during deployPasswordless sudo broken/etc/sudoers.d/90-cloud-init-users should include ubuntu ALL=(ALL) NOPASSWD:ALL

Server prerequisites checklist

Quick sanity check before first deploy:

  • DNS A record for docs.yatcogroup.com resolves to the EC2 public IP
  • SSH on port 827 works as ubuntu with key auth
  • ubuntu has passwordless sudo
  • Node 20 LTS installed
  • /var/www/ygc-docs/site/, /opt/ygc-docs/{docs,server}/ exist with correct ownership
  • /etc/ygc-docs-chat.env exists, mode 600 root, contains ANTHROPIC_API_KEY=
  • ygc-docs-chat.service enabled
  • nginx vhost in sites-enabled/, nginx -t clean
  • TLS cert covers docs.yatcogroup.com (in the shared yatcogroup-multi cert)