← Blog
Infrastructure · 8 min read

Remote AI agent development over a private VPN: Android builds on a Linux VPS

Long-running AI agents work best on a dedicated machine, not on the laptop you close at night. For Android that machine can be a plain Linux VPS: cheap, always on, and fast enough for Gradle. The catch is reaching it safely. Here is the setup I use: a private WireGuard VPN, where nothing else listens on the public internet.

Why put agents and Android builds on a Linux VPS

  • Agents run for hours. Tests, builds and refactors keep going when your laptop sleeps.
  • Android builds don't need a Mac. The Android SDK command-line tools, a JDK and Gradle run on any Linux server. Pick a VPS with enough RAM and CPU cores for Gradle — memory is usually the first bottleneck.
  • Emulators are optional. Unit tests and builds need no emulator. If you want instrumented tests on the VPS, choose a host that exposes KVM, otherwise the emulator is too slow to be useful.
  • Isolation. The agent gets its own user, its own tokens and its own disk — not your personal accounts and SSH keys.

The risk is the opposite of isolation: an SSH port or a dev server open to the whole internet. A WireGuard VPN removes that risk.

The layout

The VPS runs a WireGuard interface, wg0, on the private range 10.8.0.0/24. Each device gets its own key and a fixed address: the VPS is 10.8.0.1, your laptop 10.8.0.2, your Android phone 10.8.0.3, the CI runner 10.8.0.10. The only port open to the internet is WireGuard's UDP port — and WireGuard does not answer packets from unknown keys, so to a scanner the VPS looks silent.

Step 1 — install WireGuard and create keys

sudo apt install wireguard
wg genkey | tee vps.key | wg pubkey > vps.pub

Repeat wg genkey for every device. Private keys stay on the device they belong to; only public keys are copied around.

Step 2 — configure the VPS

/etc/wireguard/wg0.conf lists every allowed device by its public key. A device that is not listed cannot connect.

[Interface]
Address = 10.8.0.1/24
ListenPort = 51820
PrivateKey = <vps-private-key>

# laptop
[Peer]
PublicKey = <laptop-public-key>
AllowedIPs = 10.8.0.2/32

# Android phone
[Peer]
PublicKey = <phone-public-key>
AllowedIPs = 10.8.0.3/32

# CI runner
[Peer]
PublicKey = <ci-public-key>
AllowedIPs = 10.8.0.10/32
sudo systemctl enable --now wg-quick@wg0

Step 3 — connect your laptop and phone

Each device gets a small config that points at the VPS:

[Interface]
Address = 10.8.0.2/32
PrivateKey = <laptop-private-key>

[Peer]
PublicKey = <vps-public-key>
Endpoint = vps.example.net:51820
AllowedIPs = 10.8.0.0/24
PersistentKeepalive = 25

For the phone, write the same kind of file with address 10.8.0.3 and show it as a QR code for the official WireGuard app:

qrencode -t ansiutf8 < phone.conf

AllowedIPs = 10.8.0.0/24 means only VPN traffic goes through the tunnel; everything else on the device uses its normal connection.

Step 4 — firewall: default deny, then allow per peer

WireGuard decides who may join; the firewall decides what each peer may reach. Set it up while connected over the VPN (ssh agent@10.8.0.1), so enabling it cannot lock you out:

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 51820/udp
sudo ufw allow in on wg0 from 10.8.0.2 to any port 22,8080 proto tcp
sudo ufw allow in on wg0 from 10.8.0.3 to any port 8080 proto tcp
sudo ufw allow in on wg0 from 10.8.0.10 to any port 22 proto tcp
sudo ufw enable

Now SSH is reachable only from your laptop and the CI runner, the APK share only from your laptop and phone, and nothing at all from the public internet. Also set PasswordAuthentication no in sshd_config — keys only.

Step 5 — build Android on the VPS

Install a JDK and the Android SDK command-line tools for the agent user, then give agents one command to run. A fastlane lane keeps it identical to CI:

platform :android do
  # Safe for agents: no signing keys needed
  lane :check do
    gradle(task: "testDebugUnitTest")
    gradle(task: "assembleDebug")
  end
end

The agent runs bundle exec fastlane android check (or ./gradlew testDebugUnitTest assembleDebug directly) after every change. Release signing never happens here — the upload keystore and the Play service-account key live only in CI, behind a human-approved release environment, as described in the fastlane harness post.

Step 6 — get the APK onto your phone, privately

Serve the build output folder on the VPN address only:

python3 -m http.server 8080 --bind 10.8.0.1 --directory ~/app/app/build/outputs/apk/debug

With WireGuard on, open http://10.8.0.1:8080 on your phone, download the debug APK and install it. The server is bound to the VPN address and allowed only for your devices by the firewall — no public download links, no files sent through chat apps. A dev server started by an agent can be shared the same way.

Step 7 — let CI in, temporarily

If a pipeline needs to reach the VPS — say, to collect artifacts — store the CI peer's config as a secret and bring the tunnel up only for the job:

- name: Join VPN
  run: |
    sudo apt-get update && sudo apt-get install -y wireguard-tools
    echo "${{ secrets.WG_CI_CONF }}" | sudo tee /etc/wireguard/wg0.conf > /dev/null
    sudo wg-quick up wg0

# ... steps that use ssh agent@10.8.0.1 ...

- name: Leave VPN
  if: always()
  run: sudo wg-quick down wg0

The firewall rules above limit the CI peer to SSH on the VPS. If the secret ever leaks, delete that one [Peer] block on the VPS and the key is useless.

Step 8 — keep secrets off the VPS

  • The agent runs as an unprivileged agent user, not as root.
  • Its git token is scoped to the repositories it works on: push branches, open pull requests, nothing more.
  • No upload keystore, no Play Console key and no production credentials on the VPS. Debug builds are all it ever signs.

Checklist

  • Only WireGuard's UDP port is open to the internet.
  • One key and one fixed address per device; remove a [Peer] to revoke it.
  • Firewall default-deny, with rules per peer on wg0.
  • SSH with keys only, reachable only over the VPN.
  • Agents build and test with one fastlane lane; debug builds only.
  • APKs and previews bound to the VPN address.
  • CI joins for the job and disconnects at the end.
  • No release keystore or Play key on the VPS.
Few spots left Free AI audit