Provision Google Cloud VMs pre-configured for AI-assisted development. Each VM comes with Claude Code, Codex CLI, Tailscale, git repos, and your git identity — ready to SSH into and start coding.
gh release download --repo MytraAI/cloudy-ai --pattern "cloudy_darwin_$(uname -m | sed 's/x86_64/amd64/')*" --clobber && tar xzf cloudy_darwin_*.tar.gz && sudo mv cloudy /usr/local/bin/ && rm cloudy_darwin_*.tar.gzRequires the GitHub CLI (gh) authenticated with access to the MytraAI org. macOS binaries are signed and notarized.
The CLI auto-checks for updates once per 24 hours. Run cloudy update to upgrade manually.
- A GCP project with Compute Engine enabled
cloudy auth loginThis opens your browser to authenticate via Google OAuth. No additional tools required.
Requires Go 1.24+ and Node.js 22+.
git clone git@github.com:MytraAI/cloudy-ai.git
cd cloudy-ai
make buildAdd to your PATH:
ln -sf "$(pwd)/bin/cloudy" ~/bin/cloudy# Optional — explicit git identity (auto-discovered from GitHub if not set)
cloudy config set git.name "Jane Doe"
cloudy config set git.email "jane@example.com"
# Optional — SSH access from other devices (phone, tablet, etc.)
cloudy config set ssh.user mike_brevoort
cloudy config set ssh.public_key "ssh-ed25519 AAAA..."See all options:
cloudy config listcloudy createInteractive prompts walk you through VM name, size, OS, and repo selection. If GitHub is connected (via OAuth or PAT), your repos are shown as a filterable list sorted by how often you use them.
cloudy create --name my-vm --size medium --repos https://github.com/org/repo.git --yes
# Spot/preemptible — ~70% cheaper but may be preempted by GCP
cloudy create --name my-vm --size xlarge --spot --yescloudy list # List all managed VMs
cloudy ssh <name> # SSH into a VM
cloudy ssh <name> --session <s> # SSH into a specific session (worktree)
cloudy ssh forward <name> <port> # Forward local port to VM via SSH
cloudy stop <name> # Stop (preserves disk, stops billing)
cloudy start <name> # Start a stopped VM
cloudy suspend <name> # Suspend (snapshots memory to disk)
cloudy resume <name> # Resume a suspended VM
cloudy destroy <name> # Delete VM and disk
cloudy init-script # Print/set per-user init script
cloudy sessions list <name> # List sessions (git worktrees) on a VM
cloudy sessions create <name> # Create a new session
cloudy sessions delete <name> <s> # Delete a sessionConfigure an SSH public key to access VMs from any device (phone, tablet, other machines):
cloudy config set ssh.user mike_brevoort
cloudy config set ssh.public_key "ssh-ed25519 AAAA..."New VMs will have the key automatically. For existing VMs:
cloudy ssh-bootstrap <name>Then connect directly: ssh mike_brevoort@<vm-ip> or via Tailscale hostname.
The web UI includes a built-in terminal panel for SSH access directly from the browser. Once a VM's setup is complete, click Terminal to open an SSH session. The server acts as a WebSocket-to-SSH relay using managed SSH keypairs. Sessions persist across page navigation and support multiple tabs.
Drag-and-drop file upload: Drag files from your desktop onto the terminal panel to upload them to the VM. Files land at ~/uploads/ and the absolute path is automatically typed into the active terminal session — just like dragging a file onto Terminal.app on macOS.
Store a custom shell script that runs at the end of every VM provisioning:
cloudy init-script # Print current init script
cloudy init-script --edit # Open in $EDITORUse this for personal setup like dotfiles, editor config, or additional tools without modifying the shared provisioning template.
Claude Code settings (~/.claude) are automatically synced across all your VMs via a GCSFuse-backed shared filesystem. Changes made on one VM are visible on all others.
Tailscale is configured automatically via server-side OAuth when provisioning VMs. If a VM was created without Tailscale, you can install it retroactively:
cloudy tailscale-bootstrap <name>| Size | Machine Type | vCPUs | Memory | Disk |
|---|---|---|---|---|
| small | e2-standard-2 | 2 | 8 GB | 50 GB |
| medium | e2-standard-4 | 4 | 16 GB | 100 GB |
| large | e2-standard-8 | 8 | 32 GB | 200 GB |
| xlarge | c3-highcpu-22 | 22 | 22 GB | 400 GB |
Any size can be combined with --spot for spot/preemptible pricing (~70% cheaper). Set a default: cloudy config set defaults.size large
The default Cloudy image pre-bakes most dev tools into a GCE image so VMs boot fast. Two image families exist: cloudy-full (prod) and cloudy-full-dev (dev) — the server selects the right one based on CLOUDY_ENV. The startup script skips anything already installed.
- System packages (git, curl, build-essential, cmake, socat, jq, python3)
- Node.js 24.x LTS, nvm, Go, Rust, Deno
- Docker (with Buildx, Compose, docker-compose), PostgreSQL (installed, not auto-started), NATS
- kubectl, helm, k9s, Devbox, Conan
- pnpm, GitHub CLI, neovim, ripgrep, fd, htop, tmux, tree
- Claude Code (native installer, settings synced via shared filesystem)
- Codex CLI
- Tailscale (if auth key configured)
- Git credentials and identity
- Your selected repositories cloned to
~/ - Heartbeat service (reports disk usage, uptime, load to server every 60s)
- Preemption detection hook (spot VMs — notifies server on GCE preemption)
Use --os ubuntu for a vanilla Ubuntu 24.04 image (all tools installed at boot).
make init # Check tools, install Go + npm dependencies
cp .env.sample .env # Fill in values (see comments in file)
make dev # Start Go server + Vite dev servermake dev runs the Go API on :8085 and Vite on :5173 (proxying /api and /auth to Go).
The Vite dev server serves HTTPS using Tailscale-issued certs. This avoids self-signed certificate warnings and is required for Tailscale WASM browser terminals.
- Enable HTTPS certificates in Tailscale Admin → DNS
- Generate certs for your dev machine:
sudo tailscale cert --cert-file .certs/dev.crt --key-file .certs/dev.key $(tailscale status --json | jq -r '.Self.DNSName | rtrimstr(".")') sudo chown $USER .certs/dev.crt .certs/dev.key
- Set
BASE_URLin.envto your Tailscale FQDN with port 5173:BASE_URL=https://<machine-name>.<tailnet>.ts.net:5173 make dev— Vite auto-detects.certs/dev.crtand serves trusted HTTPS
Certs expire after 90 days — re-run step 2 to renew.
Cloudy has three deployment pipelines:
- Server deploy — pushes to
mainauto-deploy the web UI + API to Cloud Run - VM image build — pushes to
mainthat changevm_images/**build the prod GCE image via Packer (build-vm-image.yml). Dev images are built manually withcd vm_images && make build-dev-full. - CLI release — pushing a
v*tag builds cross-platform binaries, signs macOS builds, and publishes a GitHub Release
Run the idempotent setup script to provision all GCP resources:
./scripts/setup-production.shThis creates: Firestore database, Artifact Registry, service accounts, Workload Identity Federation, and Secret Manager secrets (prod-cloudy-google-client-id, prod-cloudy-google-client-secret, prod-cloudy-session-secret).
After running the setup script, manually create these additional optional secrets for GitHub and Tailscale integration:
# GitHub OAuth App (enables "Connect GitHub" in web UI)
echo -n "YOUR_CLIENT_ID" | gcloud secrets create prod-cloudy-github-client-id --data-file=- --project=mytra-ai-dev
echo -n "YOUR_CLIENT_SECRET" | gcloud secrets create prod-cloudy-github-client-secret --data-file=- --project=mytra-ai-dev
# Tailscale OAuth (enables centralized per-VM auth key minting)
echo -n "YOUR_CLIENT_ID" | gcloud secrets create prod-cloudy-tailscale-client-id --data-file=- --project=mytra-ai-dev
echo -n "YOUR_CLIENT_SECRET" | gcloud secrets create prod-cloudy-tailscale-client-secret --data-file=- --project=mytra-ai-devAll Secret Manager secret names are prefixed with the environment (e.g. prod-cloudy-* for production, dev-cloudy-* for development). The CLOUDY_ENV environment variable controls this prefix and defaults to dev.
The server runs on Cloud Run. To map a custom domain:
# Create domain mapping (one-time)
gcloud beta run domain-mappings create \
--service=cloudy \
--domain=cloudy.artym.net \
--region=us-central1 \
--project=mytra-ai-dev
# Add CNAME record in Cloud DNS (artym.net zone is in the telemetry-monitor project)
gcloud dns record-sets create cloudy.artym.net. \
--zone=artym-net \
--type=CNAME \
--rrdatas="ghs.googlehosted.com." \
--ttl=300 \
--project=telemetry-monitorGoogle auto-provisions the TLS certificate once the CNAME propagates.
To enable "Connect GitHub" in the web UI, create a GitHub OAuth App:
- Go to GitHub Developer Settings > OAuth Apps > New OAuth App
- Set Authorization callback URL to
https://cloudy.artym.net/auth/github/callback - After creating, note the Client ID and generate a Client Secret
- Store both as GCP secrets (see step 1 above)
When configured, users can connect their GitHub account with one click instead of manually creating a PAT. PATs are still supported and take precedence over OAuth tokens when both exist.
For local development, create a separate OAuth App with callback URL http://localhost:8085/auth/github/callback and add the credentials to your .env file (see .env.sample).
The repo requires two sets of secrets:
| Secret | Description |
|---|---|
WIF_PROVIDER |
Workload Identity Federation provider (output by setup script) |
WIF_SERVICE_ACCOUNT |
Deploy service account email (output by setup script) |
CLOUD_RUN_SA |
Runtime service account email (output by setup script) |
INITIAL_ADMIN_EMAIL |
First admin user's email |
The deploy workflow also mounts these GCP secrets as env vars at runtime: GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, SESSION_SECRET, GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET, TAILSCALE_CLIENT_ID, TAILSCALE_CLIENT_SECRET.
| Secret | Description |
|---|---|
APPLE_CERTIFICATE_BASE64 |
Base64-encoded .p12 containing Developer ID Application cert + private key |
APPLE_CERTIFICATE_PASSWORD |
Password used when exporting the .p12 |
APPLE_ID |
Apple ID email for notarization |
APPLE_ID_APP_PASSWORD |
App-specific password from appleid.apple.com |
APPLE_TEAM_ID |
10-character Apple Team ID (e.g. Q5T8FJNX57) |
APPLE_TEAM_NAME |
Certificate holder name (e.g. Brevoort Studio LLC) |
The release workflow signs and notarizes macOS binaries. This requires a Developer ID Application certificate (not "Apple Development" or "Apple Distribution").
Creating the certificate:
- Open Keychain Access > Certificate Assistant > Request a Certificate From a Certificate Authority
- Enter your email, select "Saved to disk", save the CSR file
- Go to Apple Developer Certificates
- Click +, select Developer ID Application, choose G2 Sub-CA
- Upload the CSR from step 2, download the
.cer - Double-click the
.certo install it in Keychain Access
Important: Steps 2 and 6 must happen on the same Mac so the private key is linked to the certificate.
Exporting the .p12:
The Keychain Access UI may not offer .p12 export even when the key is present. Use the command line:
# Verify the identity exists (must show "Developer ID Application: ...")
security find-identity -v -p codesigning
# Export as .p12
security export -k login.keychain-db -t identities -f pkcs12 -o /tmp/devid.p12 -P "your-password"
# Base64-encode and copy to clipboard
base64 -i /tmp/devid.p12 | pbcopy
# Clean up
rm /tmp/devid.p12Set APPLE_CERTIFICATE_BASE64 to the clipboard contents and APPLE_CERTIFICATE_PASSWORD to the password you used.
Generating an app-specific password:
- Go to appleid.apple.com > Sign-In and Security > App-Specific Passwords
- Generate a new password, use it for
APPLE_ID_APP_PASSWORD
git tag v0.2.0
git push origin v0.2.0The release workflow (.github/workflows/release.yml) runs on macos-latest and:
- Builds web assets (
npm ci && npm run build) - Cross-compiles for 5 targets via GoReleaser (darwin/linux amd64/arm64, windows amd64)
- Signs and notarizes macOS binaries with
scripts/sign-macos.sh - Creates a GitHub Release with archives and checksums
Users running older versions will see an update notice and can run cloudy update.