Ephemeral DigitalOcean Build Pipeline for Kasm Images (GitHub Actions)
Overview
If you are building Kasm images on Apple Silicon, cross-compiling large amd64 workloads (especially Go-heavy stacks) can be slow and painful.
The clean solution is to offload builds to an ephemeral amd64 DigitalOcean droplet from GitHub Actions:
- Provision droplet on demand
- Build natively on amd64 (no QEMU emulation)
- Push image to DigitalOcean Container Registry
- Destroy droplet every run
This gives you fast, repeatable builds with near-zero infrastructure drift.
Architecture
The pipeline is split into three files:
build-and-push.ymlsetup-pipeline.shREADME.md
High-level flow:
- Push to
main(or run manually) - GitHub Actions creates a temporary DO droplet
- Workflow SSHes in and runs a native docker build
- Built image is pushed to DOCR
- Droplet is destroyed with
if: always()cleanup - Optional: register image in Kasm via API (
/admin/add_image) and trigger agent pull
Why This Pattern Works
- Native amd64 performance: no ARM -> x86 emulation overhead
- Ephemeral infra: every build starts clean, no stale host state
- Predictable cost: build machine lives only for minutes
- Scalable builds: choose bigger droplets for heavier compilation jobs
build-and-push.yml (Workflow)
This workflow does the heavy lifting:
- Trigger:
pushtomain+workflow_dispatch - Provision droplet with DO API
- Poll for public IP and SSH readiness
- Build and push image from droplet
- Cleanup droplet unconditionally
You can expose droplet size as a manual input (for example s-2vcpu-4gb to s-8vcpu-16gb) so heavier builds can be accelerated on demand.
Core ideas to include in the workflow
on: push: branches: [main] workflow_dispatch: inputs: droplet_size: description: "DigitalOcean droplet size" required: true default: "s-4vcpu-8gb"# Cleanup must always run- name: Destroy droplet if: always() run: | curl -sS -X DELETE \ -H "Authorization: Bearer $DO_TOKEN" \ "https://api.digitalocean.com/v2/droplets/$DROPLET_ID"Keep Kasm registration as a separate job, gated behind secrets and usually dependent on successful push.
setup-pipeline.sh (Bootstrap Script)
Use a one-shot setup script to avoid manual secret drift. The script should:
- Create/verify DO Container Registry
- Generate SSH keypair for CI
- Register public key in DigitalOcean account
- Set all required GitHub repository secrets
Run it like:
GITHUB_REPO=your-org/your-repo ./setup-pipeline.shTypical secrets configured:
DO_TOKENDO_REGISTRY_NAMEDO_SSH_PRIVATE_KEYDO_SSH_KEY_IDDO_REGION
Then add Kasm secrets later for v2.0:
KASM_BASE_URLKASM_API_KEYKASM_API_SECRET
README.md (Operational Docs)
Your repo docs should cover:
- Pipeline architecture diagram
- Secret inventory
- One-time setup instructions
- Manual run options (droplet size)
- Failure modes + troubleshooting
- Cost model
Cost and Runtime
For short-lived CI builds (roughly 10-15 minutes), cost is usually very low (often around $0.01-$0.02 per run, depending on size/region and image pull/build time).
You only pay while the droplet exists, so strict cleanup logic is critical.
Kasm v2.0 Registration (Ready but Optional)
Once image pushes are stable, enable the Kasm integration step:
- Call Kasm API endpoint to add/register image metadata
- Trigger Kasm agents to pull new image tag
- Validate image appears in Kasm catalog
Recommended rollout:
- Start with build + push only
- Add Kasm registration in dry-run or staging
- Enable in production after first successful end-to-end tests
Hardening Recommendations
- Use unique immutable tags (for example
2026.03.01-<sha>) - Avoid
latestas deployment source of truth - Use least-privilege DO tokens
- Rotate CI SSH keys periodically
- Add timeout guards for droplet provisioning + SSH wait
- Emit droplet ID and cleanup status in logs for auditability
Final Thoughts
This pattern is one of the cleanest ways to solve ARM/x86 build friction while keeping CI reproducible and cheap. You get native amd64 speed when you need it, without maintaining permanent builder infrastructure.