Pulumi project to provision Google Cloud VMs for PostHog local development.
- One-command deployment: Spin up fully configured PostHog dev VMs
- Chrome Remote Desktop: Secure access via Google's remote desktop (no ports exposed)
- Branch support: Deploy from master or any feature branch
- Multiple VMs: Run multiple dev environments simultaneously
- Pre-configured: Docker, Flox, Python, Node.js, pnpm, mprocs all installed
- AI coding tools: Claude Code and OpenAI Codex CLI pre-installed
- Pulumi CLI:
brew install pulumi - Pulumi Cloud account:
pulumi login(free for individuals) - gcloud CLI: Installed and authenticated with
gcloud auth login - GCP Project: With Compute Engine API enabled
# Clone this repo
git clone https://github.com/andrewm4894/posthog-dev-pulumi.git
cd posthog-dev-pulumi
# Install dependencies
uv sync
# Select the dev stack
pulumi stack select dev
# Configure GCP project and region
pulumi config set gcp:project YOUR_PROJECT_ID
pulumi config set gcp:region europe-west1
# (Optional) Store RDP sudo password in Secret Manager
echo -n "YourSecurePassword" | gcloud secrets create rdp-password --data-file=-
pulumi config set rdpPasswordSecretName "rdp-password"
# (Optional) Store API keys in Secret Manager
echo -n "sk-ant-..." | gcloud secrets create anthropic-api-key --data-file=-
echo -n "sk-..." | gcloud secrets create openai-api-key --data-file=-
# Reference secret names in config (vms.yaml or Pulumi config)
pulumi config set anthropicSecretName "anthropic-api-key"
pulumi config set openaiSecretName "openai-api-key"
# Deploy
pulumi upEdit vms.yaml to define your VMs:
defaults:
machine_type: e2-standard-8
disk_size_gb: 100
posthog_branch: master
# Optional: use a custom GCE image for faster bootstraps
# os_image: "projects/YOUR_PROJECT/global/images/posthog-dev-base-YYYYMMDD"
vms:
- name: posthog-dev-1
description: "PostHog development VM"
- name: posthog-feature-x
description: "Feature X development"
posthog_branch: feature-x-branch# Single VM mode
pulumi config set vmName my-dev-vm
pulumi config set posthogBranch my-feature-branch
pulumi config set machineType e2-standard-4
pulumi config set diskSizeGb 150pulumi config set netdataClaimRooms "ROOM_ID" # Optional: Netdata room ID
# Store secrets in Secret Manager
echo -n "PASSWORD" | gcloud secrets create rdp-password --data-file=-
echo -n "TOKEN" | gcloud secrets create netdata-claim-token --data-file=-
echo -n "KEY" | gcloud secrets create anthropic-api-key --data-file=-
echo -n "KEY" | gcloud secrets create openai-api-key --data-file=-
echo -n "TOKEN" | gcloud secrets create github-token --data-file=-
# Reference secret names in config (vms.yaml or Pulumi config)
pulumi config set rdpPasswordSecretName "rdp-password"
pulumi config set netdataClaimTokenSecretName "netdata-claim-token"
pulumi config set anthropicSecretName "anthropic-api-key"
pulumi config set openaiSecretName "openai-api-key"
pulumi config set githubTokenSecretName "github-token"Grant the VM service account access to the secrets (Secret Manager Secret Accessor), e.g.:
PROJECT_NUMBER=$(gcloud projects describe "$PROJECT_ID" --format='value(projectNumber)')
SA="$PROJECT_NUMBER[email protected]"
gcloud secrets add-iam-policy-binding anthropic-api-key \
--member="serviceAccount:$SA" --role="roles/secretmanager.secretAccessor"# SSH into the VM (uses IAP tunneling - no external IP needed)
gcloud compute ssh posthog-dev-1 --tunnel-through-iap --zone=europe-west1-b
# Switch to the ph user
sudo su - ph
# Go to https://remotedesktop.google.com/headless
# Click "Begin" > "Next" > "Authorize"
# Copy the Debian Linux command and run it on the VM
# Set a PIN when prompted- Go to https://remotedesktop.google.com/access
- Click on your VM
- Enter your PIN
- You're now in the XFCE desktop
Open a terminal in the desktop and run:
phstartThis launches mprocs with all PostHog services. Access PostHog at http://localhost:8010 in the VM's browser.
Once connected to the VM as the ph user:
phstart # Start PostHog dev server (mprocs)
phattach # Attach to running mprocs session
phtail # Tail PostHog log files
phlogs # View Docker container logs
phps # Show Docker container status
phdown # Stop Docker containers
phmake help # Show all make commandsVMs have no external IP addresses. Access is via:
- SSH: IAP (Identity-Aware Proxy) tunneling - authenticated through your Google identity
- Desktop: Chrome Remote Desktop - uses Google's secure HTTPS relay
Cloud NAT provides outbound internet access for the VMs.
To speed up provisioning, you can bake a GCE image from a fully provisioned VM.
If os_image is not set, the default Ubuntu image is used (full bootstrap).
# Create an image from an existing VM
make bake-image VM=posthog-master IMAGE=posthog-dev-base
# Use it as the default image for new VMs (supports ${GCP_PROJECT})
pulumi config set baseImage "projects/${GCP_PROJECT}/global/images/posthog-dev-base"When you bake an image, the VM is marked with /etc/posthog-base-image.
On boot, the startup script detects this marker and skips heavy steps
(repo clone, Flox activation, Docker services, and image pre-pull).
Refresh the image whenever you update startup scripts or dependencies.
# Destroy all resources
pulumi destroy
# Remove stack (optional)
pulumi stack rm devgcloud compute ssh posthog-dev-1 --tunnel-through-iap --zone=europe-west1-b
sudo cat /var/log/posthog-startup.log
sudo cat /var/log/posthog-startup-timing.logsudo su - ph
docker compose -f ~/posthog/docker-compose.dev.yml ps
docker compose -f ~/posthog/docker-compose.dev.yml logs -f- Make sure you ran the setup command from https://remotedesktop.google.com/headless
- Check the service:
systemctl --user status chrome-remote-desktop - Restart it:
systemctl --user restart chrome-remote-desktop
| Resource | Cost (24/7) | Notes |
|---|---|---|
| e2-standard-8 VM | ~$200/month | Stop when not in use! |
| e2-standard-4 VM | ~$100/month | Lighter workloads |
| 100GB SSD | ~$17/month | - |
Tip: Stop VMs when not in use to save costs:
gcloud compute instances stop posthog-dev-1 --zone=europe-west1-b
gcloud compute instances start posthog-dev-1 --zone=europe-west1-b