Deploy to a VM
Ship a container to a Linux VM instead of a Kubernetes cluster; same pipeline, Docker + Caddy substrate, TLS and multi-service routing handled for you.
Not every workload wants Kubernetes. ZopDay can ship a container straight to a Linux VM as an alternative substrate; same catalog, same revisions, same live progress; but the target is a Docker host with Caddy in front instead of a cluster. You still hand over no kubeconfigs, no SSH keys, and no standing credentials.

The Virtual Machine substrate; ZopDay sets up the VM and installs Docker + Caddy automatically, so the first deploy has a container host and TLS router ready.
When a VM is the right target
- A single service (or a handful) that doesn’t need cluster orchestration
- A cost-sensitive environment where a full cluster is overkill
- A legacy or simple app you want live with TLS in minutes
For orchestration-heavy, multi-service, autoscaling workloads, deploy to a cluster instead.
How it differs from a cluster deploy
A VM substrate is a deployment space with space_type = vm. The pipeline is identical: the same claim → build (GitHub Actions) → deploy flow, only the final executor arm changes: it runs a runcommand (docker pull/run + a Caddy route update) instead of a helm release.
| Cluster substrate | VM substrate | |
|---|---|---|
| Runtime | Helm release on Kubernetes | Docker container on a Linux VM |
| Router / TLS | Ingress controller + cert-manager | Caddy (Let’s Encrypt via ACME) on the VM |
| Exec channel | kubeauth (ephemeral kubeconfig) | IAM-gated exec (SSM / IAP / Azure Run Command), no public SSH |
| Multi-tenant routing | Namespaces | Per-service Caddy site blocks on a shared host |
Baseline components
Every VM in a substrate has two idempotent components installed: Docker and Caddy. Caddy’s bootstrap config includes a :80 health responder so the cloud load balancer reports healthy from the moment install finishes; there’s no unhealthy window before the first deploy.
Caddy is the router when a VM is used as a ZopDay deploy substrate; a VM provisioned as standalone infrastructure uses NGINX instead (see Provisioning VMs).
TLS and hostnames
- TLS terminates at Caddy via Let’s Encrypt (HTTP-01 on :80, TLS-ALPN-01 on :443). No cert wrangling.
- Automatic hostname: if a deployment has no explicit expose config but its space carries wildcard domain patterns, ZopDay synthesises a stable hostname of the form
<name>-<short-id>.<space-suffix>. The short-id suffix prevents same-name collisions across deployments. The Quick Deploy “Live at” preview shows exactly the host Caddy will serve.
Multiple services on one VM
Several deployments can share one VM. Each deploy adds its own Caddy site block without touching co-resident services; their routes and containers are preserved across your deploy. Teardown is symmetric: removing one service leaves the others running. ZopDay fails a deploy that would collide on host or port rather than silently overwriting.
No public SSH
ZopDay never opens a public SSH session to your VM. The deploy runs over the cloud’s IAM-gated exec channel: aws ssm send-command, gcloud compute ssh --tunnel-through-iap, or az vm run-command, dispatched from the GitHub Actions runner, not from a ZopDay pod. Port 22 is not publicly reachable; interactive access is only through that IAM-gated channel.
Private registries
For images in a private registry (GAR / ECR / ACR), ZopDay mints a short-lived registry token server-side and injects a docker login before the pull, so the VM never holds long-lived cloud credentials.
Reliability
If the build’s success callback can’t reach ZopDay, a watchdog scrapes the build log for the canonical “successfully pushed” marker and recovers the deploy, covering both the callback-failed-after-success and the failure-but-push-completed cases.