Skip to content

Deploying apps

The restful-expose helper is the front door. Point it at a port or a static directory, give it a name, and you get a live URL with TLS in about ten seconds.

For anything that listens on a TCP port — Next.js, Node, Python, Go, Rust, doesn’t matter. Run your server bound to 127.0.0.1:<port>, then:

Terminal window
restful-expose --name api --port 3000

Your app is live at https://api.<your-slug>.restful.host/. nginx proxies to 127.0.0.1:3000. Wildcard TLS already covers it.

For anything pre-built into a directory of HTML/CSS/JS — Astro, Hugo, plain HTML, Vite output, etc. Point at the directory:

Terminal window
restful-expose --name docs --root /home/restful/docs/dist

Live at https://docs.<your-slug>.restful.host/. nginx serves the files directly.

Only ports 22, 80, 443 are open to the internet. Everything else is firewalled. Your app should bind to 127.0.0.1 (loopback) — nginx is the only path from the outside world to your app, and it talks to the loopback address.

This is also why you don’t need to think about TLS termination in your app: nginx does it, your app just speaks plain HTTP on localhost.

restful-expose writes the nginx config but doesn’t manage your process. For development you can run your server in a tmux pane and walk away. For something that should survive reboots, write a systemd unit:

/etc/systemd/system/restful-app-api.service
[Unit]
Description=My API
After=network.target
[Service]
Type=simple
User=restful
Group=restful
WorkingDirectory=/home/restful/api
ExecStart=/usr/bin/node server.js
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target

Then sudo systemctl enable --now restful-app-api. Ask Claude to do this for you and it will — there’s a skill installed on your machine that teaches Claude the conventions.

restful-expose is idempotent. Re-running it with the same --name updates the nginx config and reloads. Safe to run on every deploy.

To take down an app, delete the nginx file and reload:

Terminal window
sudo rm /etc/nginx/sites-restful/api.conf
sudo nginx -s reload

api.<slug>.restful.host is fine for prototypes and internal tools, but for anything customer-facing you probably want your own domain. See Custom domains for the flow.