Running Steve Yegge’s Beads as a Secure Multi-Developer Remote Server on GCP
Set up a secure Dolt database using GCP's Compute Engine VMs, IAP SSH tunneling & 2FA. Use Beads for a multi-developer/multi-agent setup.

Running Steve Yegge’s Beads as a Secure Multi-Developer Remote Server on GCP
I have begun using a multi-agent coding workflow with a pretty standard pattern: Plan → Implement → Validate → Iterate → Ship. This centers around skills as the instructors and Beads as the shared context and task management layer.
For those who don’t know, Beads is a agentic-first lightweight issue/project tracking tool that can use Dolt as its backend. Dolt gives you a “Git-for-SQL” database engine with a MySQL-compatible protocol.
Beads are incredibly easy to setup locally and I have been mostly using them privately in the--stealth mode. However, my fellow developers and their agents don’t then have access to the beads I create. I use beads to create breakdowns of major features but also some minor improvement tasks/TODOs which would be beneficial to share with the other developers so we have a single source of “programming tasks” to be done. Also, when moving into multi-consumer agentic workflows, a single, feature-branch independent source of truth for the context and the tasks is a must-have.
So, here I present how we setup a simple and lightweight remote Beads setup for a multi-developer and multi-agent workflow.
1. Target architecture
The architecture we built:
- Database engine: Dolt
- Application interface: Beads CLI,
bd - Topology: centralized server model
- Client access: GCP IAP TCP forwarding (Zero-Trust Access)
- Network exposure: no external VM IP, no public MySQL port
- Storage: separate persistent data disk mounted at
/mnt/disks/beads-data - Dolt mode:
--data-dirmode, so each Beads project becomes its own SQL database - Service management:
systemd - Linux service user: dedicated non-login user
beads-dolt
In this setup, a single Google Cloud VM runs Dolt in server mode. Every Beads project gets its own Dolt SQL database inside one shared data directory. Developers connect from their laptops through an Identity-Aware Proxy SSH tunnel. The connection is pretty much like this:
Developer laptop → bd CLI → local 127.0.0.1:13306 → authenticated IAP SSH tunnel → private GCP VM → Dolt bound to 127.0.0.1:3306 → persistent disk at /mnt/disks/beads-data
2. GCP Setup
The VM is created with a security-first posture.
VM Settings
In Compute Engine, create a VM with:
- Standard provisioning
- Deletion protection enabled
- Live migration enabled
- Shielded VM enabled
- Secure Boot
- vTPM
- Integrity Monitoring
- OS Login enabled
- OS Login 2FA enabled if your organization supports it
- Project-wide SSH keys blocked
We chose the e2-standard-2 machine for the demo setup.

Add a secondary data disk separate from the boot disk:

Recommended settings (use SSD or Extreme Persistent Disks for optimal performance):

Make sure the deletion rule on the data disk is “Keep disk” for data persistence (found under Add New Disk -> Advanced Configuration).

For networking settings, use the following for a private VM:
- no public HTTP
- no public HTTPS
- no public MySQL
3306 - no public SSH from
0.0.0.0/0

SetExternal IPv4 -> None under “Network interfaces”:

For security, I recommend turning all “Shielded VM” features on. Also allow access to all Cloud APIs.

For VM access, we chose to use gcloud with 2FA instead of SSH keys for ease of use and security (Zero Trust Access). The rest of tutorial is built around this choice, so bear that in mind. If you decide to use SSH keys yourself, the exact commands you should use later might be different (primarily for setting up the tunnel), but you can still keep following the tutorial.

With these settings, developers/admins will need permissions for:
- OS Login SSH access to the VM
- IAP TCP forwarding
Typical roles include, depending on your org policy:
roles/compute.osLoginorroles/compute.osAdminLoginroles/iap.tunnelResourceAccessor
These settings can be applied for principals at “IAM & Admin → IAM → Allow → Grant Access”

Coming back to the VM creation, in the Advanced settings tab, we chose to “Enable deletion protection” to ensure nobody deletes this VM accidentally.

Now, go ahead and Create your instance.
After the creation, for IAP SSH access, ensure there is a firewall rule that allows SSH from Google’s IAP TCP forwarding range:
35.235.240.0/20 -> TCP:22
This allows gcloud compute ssh --tunnel-through-iap to work without giving the VM a public IP. You can add this rule at “Network Security → Firewall Policies → Create firewall rule”

Choose a name and add Google’s IAP TCP forwarding range:

Allow TCP traffic and Create the rule:

Now you should be able to login to your instance via:
gcloud auth login
gcloud compute ssh <vm-instance> --project=<gcp-project> --zone=<vm-zone> --tunnel-through-iap
(remember to set the IAM policies)
The next steps are done on the VM unless stated otherwise. So login first!
3. Storage Setup
The VM uses two disks:
- boot disk for the operating system
- secondary persistent disk for Beads/Dolt data
Check what is the data disk (run on the VM): lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS,MODEL
In our case it looked like this:

And the stable GCP disk symlinks were (ls -l /dev/disk/by-id/ | grep google):

So the data disk was dev/disk/by-id/google-beads-disk .
Format and Mount the Data Disk
On the VM, set the data disk variable: export DATA_DEV=/dev/disk/by-id/google-beads-disk
Check that the disk has no existing filesystem signatures:sudo wipefs -n "$DATA_DEV"
Format it as ext4: sudo mkfs.ext4 -m 0 -E lazy_itable_init=0,lazy_journal_init=0,discard "$DATA_DEV"
Create the mount point:sudo mkdir -p /mnt/disks/beads-data
Mount it:sudo mount -o discard,defaults "$DATA_DEV" /mnt/disks/beads-data
Verify the mount:df -hT /mnt/disks/beads-data
Also inspect block devices again:lsblk -f

Persist the mount in /etc/fstab:UUID="$(sudo blkid -s UUID -o value "$DATA_DEV")"
Then append the mount entry:echo "UUID=$UUID /mnt/disks/beads-data ext4 discard,defaults,nofail 0 2" | sudo tee -a /etc/fstab
Reload systemd and verify the mount configuration:
sudo systemctl daemon-reload
sudo findmnt --verify
Test remounting:
sudo umount /mnt/disks/beads-data
sudo mount -a
df -hT /mnt/disks/beads-data

Troubleshooting: Boot Disk and Data Disk May Be Reversed
Do not assume the boot disk is always /dev/sda. In our case:
/dev/sdbwas the boot disk/dev/sdawas the data disk
Always verify with lsblk -f and ls -l /dev/disk/by-id/ | grep google .
Use the stable /dev/disk/by-id/google-... path rather than guessing /dev/sda or /dev/sdb.
4. Add Cloud NAT for Outbound Internet
Because the VM we configured had no external IP (outbound internet path), apt package installations failed. Since we want a secure deployment, the fix is not to add an external IP. The better solution is Cloud NAT.
Cloud NAT gives the private VM outbound internet access while preserving:
- no external VM IP
- no public SSH
- no public database port
- IAP-only administrative access
Discover Network and Subnet
From a local machine (not the VM) with gcloud, run:
gcloud compute instances describe <instance> --project=<project> --zone=<vm-zone> --format="get(networkInterfaces[0].network,networkInterfaces[0].subnetwork)"
The result shows your:
- project
- region
- zone
- network
- subnet
Create Cloud Router and NAT
Create a Cloud Router if it doesn’t exist in your project:
gcloud compute routers create beads-nat-router --project=<project> --region=<region> --network=default
Create Cloud NAT:
gcloud compute routers nats create beads-nat --project=<project> --router=beads-nat-router --region=<region> --nat-all-subnet-ip-ranges --auto-allocate-nat-external-ips
This gives private VMs in the default network outbound internet access through NAT. Inbound access remains blocked except through IAP/Google-controlled paths. This setup permits all VMs in the project/zone for outbound traffic, so be sure to verify if this appropriate in your context.
Verify the NAT:
gcloud compute routers nats describe beads-nat --project=<project> --router=beads-nat-router --region=<region>

Once NAT was active, the required package downloads and GitHub downloads should work.
Troubleshooting: No Cloud NAT
If installing Dolt or Debian packages fails with network timeouts, check whether the VM has outbound internet. Cloud NAT is my recommended approach to allow outbound connectivity. There are some other ways to install Beads and Dolt to the VM without Cloud NAT, such as copying the installation files via the tunnel we have created.
5. Install Dolt
On the VM, update package lists:
sudo apt-get update
Install basic tools:
sudo apt-get install -y curl ca-certificates default-mysql-client
Download the Dolt installer:
curl -fsSL https://github.com/dolthub/dolt/releases/latest/download/install.sh -o /tmp/dolt-install.sh
Run the installer:
sudo bash /tmp/dolt-install.sh
Verify the installation:
which dolt
dolt version
In our case, Dolt installed successfully as:
/usr/local/bin/dolt
with version:
dolt version 2.0.3
6. Create a Dedicated Dolt Service User
It is not recommended to run Dolt as root for obvious reasons. Rather, let’s create a system user:
sudo useradd --system --home-dir /var/lib/beads-dolt --create-home --shell /usr/sbin/nologin beads-dolt
Give it ownership of the data directory and its home directory:
sudo chown -R beads-dolt:beads-dolt /mnt/disks/beads-data
sudo chown -R beads-dolt:beads-dolt /var/lib/beads-dolt
Configure Dolt identity for commits and metadata:
sudo -u beads-dolt HOME=/var/lib/beads-dolt dolt config --global --add user.name "Beads Dolt Server"
sudo -u beads-dolt HOME=/var/lib/beads-dolt dolt config --global --add user.email "beads-dolt@localhost"
Verify the config:
sudo -u beads-dolt HOME=/var/lib/beads-dolt dolt config --global --list

7. Create the Dolt systemd Service
Create the service file:
sudo nano /etc/systemd/system/beads-dolt.service
Paste this service definition:
[Unit]
Description=Beads Dolt SQL Server
Documentation=https://github.com/dolthub/dolt
After=network-online.target
Wants=network-online.target
RequiresMountsFor=/mnt/disks/beads-data
[Service]
Type=simple
User=beads-dolt
Group=beads-dolt
Environment=HOME=/var/lib/beads-dolt
WorkingDirectory=/mnt/disks/beads-data
ExecStart=/usr/local/bin/dolt sql-server --data-dir /mnt/disks/beads-data --host 127.0.0.1 --port 3306
Restart=on-failure
RestartSec=5
LimitNOFILE=65535
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
ReadWritePaths=/mnt/disks/beads-data /var/lib/beads-dolt
[Install]
WantedBy=multi-user.target
Reload systemd:sudo systemctl daemon-reload
Enable and start the service:sudo systemctl enable --now beads-dolt
Check service status:sudo systemctl status beads-dolt --no-pager

Check recent logs:sudo journalctl -u beads-dolt -n 100 --no-pager

Confirm Dolt is listening only on loopback:sudo ss -ltnp | grep 3306

This is the important safety check. You want Dolt bound to 127.0.0.1:3306, not 0.0.0.0:3306.
8. Test Dolt Locally on the VM
Run:mysql -h 127.0.0.1 -P 3306 -u root -e "SHOW DATABASES;"
Before creating any Beads databases, the expected databases are:

This confirms Dolt SQL server is working locally.
9. Open an IAP Tunnel From the Developer Machine
From the local developer machine (not the VM), run:
gcloud compute ssh <instance> --project=<project --zone=<vm-zone> --tunnel-through-iap -- -N -L 127.0.0.1:13306:127.0.0.1:3306
Leave this terminal open. This maps:
local 127.0.0.1:13306 -> IAP SSH tunnel -> VM 127.0.0.1:3306
I use local port 13306 rather than 3306 to avoid conflicts with a local MySQL or MariaDB server.
Optionally test from the local machine:mysql -h 127.0.0.1 -P 13306 -u root -e "SHOW DATABASES;"
10. Initialize a Remote Beads Project
On the local developer machine again, create a project directory:
mkdir -p ~/remote-beads-test && cd ~/remote-beads-test
Assuming you have Beads installed, initialize Beads against the remote Dolt server (replace remote_beads_test with your database name if you want to):
bd init --server --external --database remote_beads_test --server-host 127.0.0.1 --server-port 13306
I recommend setting auto-export to “n”, we experienced some version control hiccups if it was set to “y”.

On the VM, verify the database exists:
mysql -h 127.0.0.1 -P 3306 -u root -e "SHOW DATABASES;"
The database list should now include remote_beads_test or whatever you named your database as:

Also verify the directory exists:sudo ls -la /mnt/disks/beads-data
You should see your database name there(remote_beads_test in our case):

11. Test Beads
First, with the tunnel running, run bd status on the local machine:

Create a test issue:
bd create "Test issue for remote Beads"

bd list :

bd show <issue> :

As we can see, the remote server works! The issue has the correct owner as well.
You may stop here, or read along for some recommended security tightenings.
Troubleshooting
Make sure all Beads users have up-to-date versions of Beads and Dolt installed. Make sure the server also has an up-to-date version of Dolt. Version mismatches might cause issues when connecting to the database and when managing issues.
(12.) Tighten Data Directory Permissions
After Dolt generated its config, we can tighten permissions.
On the VM:
sudo chmod 600 /mnt/disks/beads-data/config.yaml
sudo chown beads-dolt:beads-dolt /mnt/disks/beads-data/config.yaml
sudo chmod 750 /mnt/disks/beads-data
Verify:
sudo ls -la /mnt/disks/beads-data
The final shape should include:
/mnt/disks/beads-dataowned bybeads-dolt:beads-doltconfig.yamlowned bybeads-dolt:beads-doltconfig.yamlwith mode600- project directories such as
remote_beads_test

Restart Dolt:
sudo systemctl restart beads-dolt
Check status:
sudo systemctl status beads-dolt --no-pager
Verify the database is still visible:
mysql -h 127.0.0.1 -P 3306 -u root -e "SHOW DATABASES;"
Troubleshooting: Permission denied When Listing the Data Directory
After running:
sudo chmod 750 /mnt/disks/beads-data
a normal SSH user may get:
ls: cannot open directory '/mnt/disks/beads-data': Permission denied
That is expected. The directory belongs to beads-dolt.
Use:
sudo ls -la /mnt/disks/beads-data
The Dolt service still works because it runs as beads-dolt.
About the secure_file_priv Warning
Dolt logged this warning:
secure_file_priv is set to "", which is insecure.
It also warned:
Any user with GRANT FILE privileges will be able to read any file which the sql-server process can read.
This warning did not block functionality.
We checked whether Dolt 2.0.3 exposed a direct flag:
dolt sql-server --help | grep -i secure
The output showed TLS-related settings, but not a direct secure-file-priv option:
listener.require_secure_transport
listener.tls_cert
listener.tls_key
So we left the service as-is.
This is acceptable for the current architecture because:
- Dolt runs as
beads-dolt, notroot - Dolt listens only on
127.0.0.1 - the VM has no external IP
- developers must have IAM/IAP/SSH access before reaching SQL
- the data directory permissions are restricted
Verify no accidental systemd override was active:
sudo systemctl cat beads-dolt
The clean active command was:
ExecStart=/usr/local/bin/dolt sql-server --data-dir /mnt/disks/beads-data --host 127.0.0.1 --port 3306
13. Final Verification
On the VM, check service status using sudo systemctl status beads-dolt --no-pager :

Check the listener sudo ss -ltnp | grep 3306 :

Inspect the data directory sudo ls -la /mnt/disks/beads-data :

List databases mysql -h 127.0.0.1 -P 3306 -u root -e "SHOW DATABASES;" :

14. Conveniences
After the setup, we created a shareable tunneling script for developers to use when wanting to connect to the remote Beads database. I can’t share the script directly since it contains a few confidential details like our project ids and instance ids, but I believe you can scrape a script together from the snippets in this article quite easily.
The daily developer workflow is now something like this:
- Start the tunnel:
beads-tunnel
- In another terminal, enter the Beads project:
cd /path/to/beads/project
3.a. If it is an existing project, use the Beads commands directly.
bd status
3.b. For a new project/database, create a directory:
mkdir -p ~/projects/my-new-beads-project && cd ~/projects/my-new-beads-project
Initialize Beads:
bd init --server --external --database my_new_beads_project --server-host 127.0.0.1 --server-port 13306
3.c. For a locally new project but with an existing Beads database (e.g. new developer joining):
- (Git clone)
- If the repo already contains the Beads metadata/config and you have the tunnel open, you can usually go straight to
bd status - If the Beads metadata is corrupted/missing (e.g. gitignored) or you just want to use Beads outside of a specific project for collaboration, you can use
bd init --server --external --database remote_beads_project --server-host 127.0.0.1 --server-port 13306(IMPORTANT: the database name should match the remote database name) - If not in a tracked repository, when Beads asks
Contributing to someone else's repo? [y/N]:the new developer should answern. - That’s it!
15. Recommended Next Hardening Steps
- Add a snapshot schedule for
google-beads-disk. - Restrict IAP and OS Login IAM to a specific developer group.
- Document Beads database naming conventions.
- Monitor disk usage with
df -hT /mnt/disks/beads-data. - Keep OS packages updated with
sudo apt-get updateandsudo apt-get upgrade.
Conclusion
Thank you for reading through! I hope this article provided enough detail into how we provisioned a Beads server using GCP so you can do the same in your context.
In summary:
- Provision a VM with a persistent (and fast) disk
- Define your networking and access protocol to your security preferences (we used GCP’s IAP tunneling via gcloud and 2FA)
- Ensure external connections are available for the VM (via e.g. Cloud NAT) for package installations
- Install and run Dolt (we used systemd with a specific user role)
- Create a tunnel to your VM (gcloud compute ssh)
- Point Beads to your tunnel (
bd init --server --external --database remote_beads_project --server-host 127.0.0.1 --server-port 13306)
I’m sure there are plenty of optimizations to be made here, but this simple setup was enough for us. You could utilize Cloud Run for more cost-effectiviness for example. I’ll leave these up to you :)
Happy vibing! -R
메타데이터
- post_id
- 5268f4dc2ddc
- slug
- running-steve-yegges-beads-as-a-secure-multi-developer-remote-server-on-gcp-5268f4dc2ddc
- url
- https://medium.com/@roope.paukku/running-steve-yegges-beads-as-a-secure-multi-developer-remote-server-on-gcp-5268f4dc2ddc
- canonical_url
- https://medium.com/@roope.paukku/running-steve-yegges-beads-as-a-secure-multi-developer-remote-server-on-gcp-5268f4dc2ddc
- author_url
- https://medium.com/@roope.paukku
- status
- ok
- fetched_at
- 2026-06-09 15:37:30