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.comresolves to the EC2 public IP. - SSH on port 827 as
ubuntuwith key auth and passwordlesssudo(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/odoo17provides the HTTP→HTTPS redirect, so this runbook only adds the :443 vhost fordocs.
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
| Path | Owner | Purpose |
|---|---|---|
/var/www/ygc-docs/site | ubuntu:ubuntu | Docusaurus build output, served by nginx |
/opt/ygc-docs/docs | ubuntu:ubuntu | Source markdown — chat server reads at startup |
/opt/ygc-docs/server | ubuntu:ubuntu | Chat 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:
- Runs
npm run buildlocally. - rsyncs
build/→ubuntu@docs.yatcogroup.com:/var/www/ygc-docs/site/. - rsyncs
docs/→ubuntu@docs.yatcogroup.com:/opt/ygc-docs/docs/(the chat corpus). sudo systemctl restart ygc-docs-chatso the chat server reloads the docs.
The --delete rsync flag ensures removed pages don't linger.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Host key verification failed on first deploy | Laptop hasn't seen this hostname yet | ssh-keyscan -p 827 docs.yatcogroup.com >> ~/.ssh/known_hosts |
Permission denied (publickey) | SSH key not in authorized_keys for ubuntu | Add your pubkey on the server |
404 on every page after deploy | Wrong root or try_files chain | Check the location / block in the nginx vhost — should fall back to /404.html |
| Mixed-content warnings | Loaded over HTTP | The 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 running | sudo 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 siblings | Move docs/ to be a sibling of server/ under /opt/ygc-docs/ |
| Cert renewal succeeds but nginx serves old cert | nginx didn't reload after renewal | Verify /etc/letsencrypt/renewal-hooks/deploy/reload-nginx exists and is executable |
403 Forbidden on a static page | Doc-root owner/perm issue | Re-run the install -d from step 2; nginx needs o+rx on every parent dir |
| Sudo password prompt during deploy | Passwordless 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.comresolves to the EC2 public IP - SSH on port 827 works as
ubuntuwith key auth -
ubuntuhas passwordless sudo - Node 20 LTS installed
-
/var/www/ygc-docs/site/,/opt/ygc-docs/{docs,server}/exist with correct ownership -
/etc/ygc-docs-chat.envexists, mode 600 root, containsANTHROPIC_API_KEY= -
ygc-docs-chat.serviceenabled - nginx vhost in
sites-enabled/,nginx -tclean - TLS cert covers
docs.yatcogroup.com(in the sharedyatcogroup-multicert)