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:packagesscope. Create athttps://github.com/settings/tokens/new?scopes=repo,read:packages. - Your local pubkey (
~/.ssh/id_ed25519.puborid_rsa.pub) — gets seeded into the dev user automatically.
Provision a VPS (skip if you already have one):
gon infra:aws-ec2 --alias=dev01Or buy a Hetzner CX22 manually and note the IP.
Point DNS at the new IP — both the apex and a wildcard:
A dev01.example.com → IPA *.dev01.example.com → IPWildcard covers
vite.*andmail.*automatically. Verify before continuing:dig +short dev01.example.com vite.dev01.example.com mail.dev01.example.comBootstrap the VPS as a dev environment:
gon server:dev:setup IP \--alias=dev01 \--email=ssl@example.com \--ghcr-token=ghp_xxxxx \--shared-pat=ghp_yyyyyInstalls Docker, Traefik+ACME on 80/443, creates shared
devuser, installs gon-cli on the VPS, seeds the team's GitHub PAT. Idempotent — safe to re-run after gon-cli upgrades.Add the rest of the team's SSH keys:
gon server:dev:keys add dev01 --key=~/keys/honza.pub --label=honzagon server:dev:keys add dev01 --key=~/keys/petr.pub --label=petrgon server:dev:keys list dev01Print a copy-paste SSH config block — once each dev does this, VS Code Remote-SSH sees
dev01-devas a one-clicker:gon server:dev:ssh-config dev01# Each dev pastes the printed block into ~/.ssh/configDeploy a project onto the VPS:
gon server:dev:add-project myapp \--host=dev01 \--repo=rozklad/myapp \--domain=dev01.example.comClones the repo, generates
.env+ devgon.jsondeployment block, runsgon install+gon upon the VPS. First run takes 5–15 min (composer install + image build + ACME issuance).Verify the environment:
gon server:dev:info myapp # URLs, SSH cmd, DB creds, container statusescurl -I https://dev01.example.com # HTTP/2 200, Let's Encrypt certEach dev opens the project in VS Code:
code --remote ssh-remote+dev01-dev /home/dev/projects/myappFrom 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/.vueauto-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/gonsymlink that non-login SSH needs. - Browser cert warning — DNS not propagated yet. Wait 5–15 min, then
cd /opt/traefik && sudo docker compose restartto retry ACME. - "Vite manifest not found" — Vite container crashed because of inotify limit (8192 default is way too low for Laravel + vendor). The
dev:setupbump (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 statusbefore pulling someone's work; for parallel features spin up a second project on another subdomain.
Full command reference: gon server:dev.