Skip to content
All workCASE STUDY · 03 / 03
BackendA spare phone as a backend host

nas-deploy

Runs the Vithos API on a spare 2018 Android phone, with public HTTPS through Cloudflare Tunnel and a deploy on every push to main.

STACK
Node.jsCloudflare TunnelGitHub ActionsTermuxTailscaletmuxShell
The phone running Termux, with tmux sessions for the tunnel, the Vithos API and the deploy webhook
01 — The problem

Vithos needed an always-on backend, but a VPS is a monthly bill for a side project. A spare Android phone draws little power and has a built-in battery as backup, but it sits behind a home router, often on CGNAT, and Android keeps trying to stop background apps.

02 — Approach
  1. 01The API runs in Termux on the phone. Cloudflare Tunnel keeps an outbound connection to Cloudflare's edge and forwards a public hostname to localhost, so no inbound ports are needed.
  2. 02Every push to main triggers GitHub Actions, which POSTs to a small Node.js webhook guarded by a shared secret compared in constant time. The webhook runs the deploy script and returns its exit status and output, so a failed deploy fails the CI job.
  3. 03The deploy script stops on the first error and builds before restarting, so a broken build never replaces the running API. tmux keeps the API, tunnel and webhook alive, and Termux:Boot recreates them after a reboot.
  4. 04The first listener was the adnanh/webhook Go binary, which crashed with SIGSYS on Android: the Go runtime's process-spawning path hit Android's syscall restrictions. It was replaced with a dependency-free Node.js server that lives in the repo.
Architecture
  1. 1PushA push to main starts a GitHub Actions job
  2. 2WebhookAuthenticated POST /deploy through the tunnel
  3. 3Deploygit pull, pnpm install, pnpm build, then restart
  4. 4ServeAPI on the phone, public via Cloudflare Tunnel
  5. 5OperateSSH over Tailscale, /stats telemetry, UptimeRobot
03 — Highlights
  • Public HTTPS through an outbound Cloudflare Tunnel, so it works behind CGNAT with no ports opened on the router.
  • Push-to-deploy: GitHub Actions calls an authenticated webhook that pulls, installs, builds and restarts the API, and gets the real exit status and output back.
  • Builds before restarting, so a failed install or build leaves the running version untouched.
  • Survives reboots: Termux:Boot takes a wake lock and recreates the tmux sessions for the API, tunnel and webhook.
  • Authenticated /stats endpoint reports battery, storage, memory and load from the phone; SSH is reachable only over Tailscale.
NEXT PROJECTgoComp