Setup Dev VPS for the Team

Move slow local Docker Desktop development to a remote VPS. Devs edit through VS Code Remote-SSH — builds, containers, and Vite HMR all run on the server.

Before you start

  • A fresh VPS — Hetzner CX22 (~€4/mo), AWS t3.small, or similar. Don't reuse a server already running server:setup + gon release (port 80/443 conflict).
  • A domain with wildcard DNS pointing at the VPS IP (e.g. dev01.example.com + *.dev01.example.com).
  • A classic GitHub PAT with repo + read:packages scope. Create at https://github.com/settings/tokens/new?scopes=repo,read:packages.
  • Your local pubkey (~/.ssh/id_ed25519.pub or id_rsa.pub) — gets seeded into the dev user automatically.
  1. Provision a VPS (skip if you already have one):

    gon infra:aws-ec2 --alias=dev01

    Or buy a Hetzner CX22 manually and note the IP.

  2. Point DNS at the new IP — both the apex and a wildcard:

    A dev01.example.com IP
    A *.dev01.example.com IP

    Wildcard covers vite.* and mail.* automatically. Verify before continuing:

    dig +short dev01.example.com vite.dev01.example.com mail.dev01.example.com
  3. Bootstrap the VPS as a dev environment:

    gon server:dev:setup IP \
    --alias=dev01 \
    --email=ssl@example.com \
    --ghcr-token=ghp_xxxxx \
    --shared-pat=ghp_yyyyy

    Installs Docker, Traefik+ACME on 80/443, creates shared dev user, installs gon-cli on the VPS, seeds the team's GitHub PAT. Idempotent — safe to re-run after gon-cli upgrades.

  4. Add the rest of the team's SSH keys:

    gon server:dev:keys add dev01 --key=~/keys/honza.pub --label=honza
    gon server:dev:keys add dev01 --key=~/keys/petr.pub --label=petr
    gon server:dev:keys list dev01
  5. Print a copy-paste SSH config block — once each dev does this, VS Code Remote-SSH sees dev01-dev as a one-clicker:

    gon server:dev:ssh-config dev01
     
    # Each dev pastes the printed block into ~/.ssh/config
  6. Deploy a project onto the VPS:

    gon server:dev:add-project myapp \
    --host=dev01 \
    --repo=rozklad/myapp \
    --domain=dev01.example.com

    Clones the repo, generates .env + dev gon.json deployment block, runs gon install + gon up on the VPS. First run takes 5–15 min (composer install + image build + ACME issuance).

  7. Verify the environment:

    gon server:dev:info myapp # URLs, SSH cmd, DB creds, container statuses
    curl -I https://dev01.example.com # HTTP/2 200, Let's Encrypt cert
  8. Each dev opens the project in VS Code:

    code --remote ssh-remote+dev01-dev /home/dev/projects/myapp

    From the Remote-SSH terminal, regular gon commands work as usual: gon shell, gon artisan migrate, gon logs -f, gon test, git push. Edits in .blade.php / .vue auto-refresh in the browser through Vite HMR over wss.

Database access from local TablePlus / DataGrip

MySQL has no public port — connect through an SSH tunnel:

ssh -L 33060:127.0.0.1:3306 dev01-dev -N

Then in TablePlus: 127.0.0.1:33060, credentials from gon server:dev:info myapp.

Adding a second project (or environment)

One VPS can host multiple projects — each gets its own subdomain and DB volume. Pick distinct domains to avoid Traefik routing collisions:

gon server:dev:add-project secondapp \
--host=dev01 \
--repo=rozklad/secondapp \
--domain=second.dev01.example.com

Tearing down

gon server:dev:remove myapp # Drop project + volumes (with confirmation)
gon server:dev:remove myapp --keep-volumes # Preserve DB if you'll reinstall
 
# Decommission the whole VPS (AWS path):
gon infra:destroy --alias=dev01

Common gotchas

  • "command not found: gon" over SSH — re-run server:dev:setup; it creates /usr/local/bin/gon symlink that non-login SSH needs.
  • Browser cert warning — DNS not propagated yet. Wait 5–15 min, then cd /opt/traefik && sudo docker compose restart to retry ACME.
  • "Vite manifest not found" — Vite container crashed because of inotify limit (8192 default is way too low for Laravel + vendor). The dev:setup bump (fs.inotify.max_user_watches=524288) handles this; if missing, fix manually.
  • Multi-dev branch coordination — shared worktree means one branch at a time. Use git status before pulling someone's work; for parallel features spin up a second project on another subdomain.

Full command reference: gon server:dev.