Script-based Setup (setup-server.sh)¶
For a non-Docker, bare-metal deployment on a fresh Ubuntu host, Doable ships an automated installer: setup-server.sh.
What it installs¶
On a clean Ubuntu 22.04 or 24.04 server, run as root:
It will install and configure:
| Component | Purpose |
|---|---|
| Node.js 22 | Runtime |
| pnpm | Package manager |
PostgreSQL 16 + extensions (pgvector, pgcrypto, pg_trgm) |
Database |
| Caddy | Reverse proxy with automatic Let's Encrypt SSL |
| cloudflared | Cloudflare Tunnel β public access without opening ports |
| tmux | Multiplexer that holds the API/Web/WS processes |
| fail2ban | SSH brute-force protection |
| UFW | Firewall (deny-all, allow only SSH) |
| Chromium dependencies | For Puppeteer thumbnail rendering |
| Swap file | Helps small VPSes survive build spikes |
systemd unit doable.service |
Auto-start on boot, wraps the tmux session |
systemd unit cloudflared.service |
Auto-start the tunnel |
What it asks you for¶
The script is interactive and prompts for:
- Domain (e.g.
app.example.com) β required. - API subdomain (default
api) βapi.example.com. - WS subdomain (default
ws) βws.example.com. - GitHub repo to clone (default
doable-me/doable). - Database password (default
doable). - Optional: GitHub / Google OAuth, Anthropic / OpenAI keys, Stripe keys.
Random secrets (JWT_SECRET, ENCRYPTION_KEY, INTERNAL_SECRET) are generated automatically with openssl rand -hex.
What runs after install¶
systemd
βββ doable.service # Wraps a tmux session named "doable"
βββ window: api # tsx watch services/api/src/index.ts
βββ window: web # next start -H 127.0.0.1
βββ window: ws # tsx watch services/ws/src/index.ts
βββ cloudflared.service # Cloudflare Tunnel β 127.0.0.1:* services
βββ caddy.service # Reverse proxy with SSL
βββ postgresql.service # Database
All application services bind to 127.0.0.1 only. Public access goes through Cloudflare Tunnel and/or Caddy. See Network Binding.
Day-2 operations¶
# Watch a window in tmux
tmux attach -t doable
# Ctrl-b 0/1/2 to switch windows; Ctrl-b d to detach
# Restart everything
systemctl restart doable
# Update Doable
cd /root/doable
git pull
pnpm install
pnpm db:migrate
systemctl restart doable
The API and WS services use tsx watch β file changes are picked up automatically without a build step. The web app needs a rebuild for production:
Disabling components¶
| You don't want⦠| Edit |
|---|---|
| Cloudflare Tunnel | Skip the cloudflared login prompt; use Caddy directly with a public IP |
| OAuth | Leave the OAuth prompts blank β email/password still works |
| Stripe billing | Leave Stripe prompts blank β credit system runs in disabled mode |
Verifying the install¶
Run these after the script finishes:
# All services bound to localhost only?
ss -tlnp | grep -v 127.0.0.1
# Should show only sshd on 0.0.0.0:22
# Caddy serving?
curl -I https://your-domain/
# API healthy?
curl https://api.your-domain/health
Troubleshooting¶
See Troubleshooting. The most common issues:
- PostgreSQL connection failed β
setup-server.shwrites the password to/root/doable/.env. Compare it topg_hba.confand make sure it matches theDATABASE_URL. - Port 80/443 already in use β another web server is running;
systemctl stopit before installing Caddy. cloudflaredlogin fails β re-run interactively:cloudflared tunnel login.
β For the Docker path see Docker Setup.