Partle Deployment & Operations¶
Server overview¶
- Host: Hetzner VM
91.98.68.236(Ubuntu 24.04) - Domain:
partle.rubenayla.xyz - Deploy user:
deploy(passwordless sudo) - App paths:
/srv/partle/backend,/srv/partle/frontend - Database: PostgreSQL
postgresql://[email protected]:5432/partle
Firewall & security¶
Default policy: deny all incoming, allow all outgoing.
| Port | Allowed from | Purpose |
|---|---|---|
| 22/tcp | Anywhere | SSH (key-only, passwords disabled) |
| 80/tcp | Cloudflare IPs only | HTTP |
| 443/tcp | Cloudflare IPs only | HTTPS |
- Direct IP access is blocked — all web traffic must go through Cloudflare
- SSH password auth disabled via
/etc/ssh/sshd_config.d/no-password.conf - Cloudflare IP ranges managed with
ufwrules (see.agents/backend.mdfor operations)
Quick deploy¶
Automated
/srv/partle/scripts/deploy.sh
Manual checklist
# Backup
pg_dump -h 91.98.68.236 -U partle_user partle > backup_$(date +%Y%m%d).sql
tar -czf frontend_dist_backup_$(date +%Y%m%d).tar.gz -C /srv/partle/frontend dist
# Pull latest
cd /srv/partle
git fetch origin main && git pull origin main
# Backend update
cd backend
uv sync
uv run alembic upgrade head
sudo systemctl restart partle-backend
# Frontend update
cd ../frontend
npm ci
npm run build
# Reload reverse proxy
sudo nginx -t && sudo systemctl reload nginx
# Smoke tests
curl -s http://localhost:8000/health | jq
curl -I https://partle.rubenayla.xyz
Service management¶
Backend (FastAPI + systemd)¶
sudo systemctl status partle-backend
sudo systemctl restart partle-backend
sudo journalctl -u partle-backend -f
# unit file: /etc/systemd/system/partle-backend.service
Frontend (static files via Nginx)¶
- Build output:
/srv/partle/frontend/dist/ - Config:
/etc/nginx/sites-enabled/partle.rubenayla.xyzcd /srv/partle/frontend && npm run build sudo nginx -t && sudo systemctl reload nginx
Monitoring & health¶
# Manual checks
curl -s http://localhost:8000/health | jq
systemctl status partle-backend nginx
curl -I https://partle.rubenayla.xyz
# Optional cron health check
*/5 * * * * /srv/partle/scripts/health_check.sh
tail -f /var/log/partle/health_check.log
Troubleshooting snippets¶
Backend won’t start¶
sudo lsof -i :8000
sudo journalctl -u partle-backend -n 50
uv --version
psql -h 91.98.68.236 -U partle_user -d partle -c "SELECT 1"
Frontend blank / stale¶
ls -la /srv/partle/frontend/dist/
sudo tail -f /var/log/nginx/error.log
sudo nginx -t
# If using Cloudflare -> Purge cache
Database issues¶
psql -h 91.98.68.236 -U partle_user -d partle
grep DATABASE_URL /srv/partle/backend/.env
sudo ufw status
Rollback¶
cd /srv/partle
cat backups/last_deployment_*.commit
git reset --hard <COMMIT_HASH>
rm -rf frontend/dist
tar -xzf backups/frontend_dist_<TIMESTAMP>.tar.gz -C frontend
cd backend && uv sync
sudo systemctl restart partle-backend
sudo systemctl reload nginx
Publishing the documentation site¶
MkDocs outputs static HTML under site/. Serve it from /documentation on Hetzner with Nginx.
- Build the docs locally (or on the server). Every
deploy.shrun already does this, but you can trigger it manually for a fresh server:cd backend uv sync --extra docs uv run mkdocs build -f ../mkdocs.yml -d /srv/partle/docs_site.tmp rsync -av --delete /srv/partle/docs_site.tmp/ /srv/partle/docs_site/ rm -rf /srv/partle/docs_site.tmp - Point Nginx at the repo-managed config once (pick
nginx-fixed.confornginx-optimized.conf):sudo ln -sf /srv/partle/nginx-fixed.conf /etc/nginx/sites-enabled/partle.rubenayla.xyz # or sudo ln -sf /srv/partle/nginx-optimized.conf ... - Ensure the config contains the documentation blocks:
location = /documentation { return 301 /documentation/; } location /documentation/ { alias /srv/partle/docs_site/; try_files $uri $uri/ /index.html; add_header Cache-Control "no-cache"; } - Reload Nginx:
sudo nginx -t && sudo systemctl reload nginx.
You can integrate these steps into deploy.sh so the docs publish automatically during deployment.
Deploy checklists¶
Pre-deploy
- Tests pass locally
- Alembic migrations verified
- Environment variables confirmed
- Secrets absent from git
- git status clean
Post-deploy
- curl http://localhost:8000/health returns 200
- curl -I https://partle.rubenayla.xyz succeeds
- curl http://localhost:8000/v1/products/ works
- journalctl -u partle-backend -n 50 clean
- Monitor logs for ~15 minutes
Historical server bootstrap¶
adduser --disabled-password --gecos '' deploy
usermod -aG sudo -s /bin/bash deploy
echo 'deploy ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/deploy
install -d -m 700 /home/deploy/.ssh
install -m 600 /root/.ssh/authorized_keys /home/deploy/.ssh/authorized_keys
chown -R deploy:deploy /home/deploy/.ssh
sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
systemctl restart ssh