Tugboat is a system for orchestrating virtual machines in a Kubernetes‑like manner.
Tugboat is a VM orchestration tool that provides Kubernetes‑style functionality while avoiding the heaviness of KubeVirt and the complexity of OpenStack.
It aims to manage VMs declaratively—similar to Kubernetes—using a minimal set of components: etcd, tugboat‑apiserver, tugboat‑agent, tugboat‑runtime, tugboat‑scheduler, and tugboat‑controller‑manager.
Existing VM orchestration systems come with significant challenges:
- Heavy due to running VMs on top of Kubernetes (double layering)
- Complex CRDs and libvirt integration
- Too many components for individuals or small teams
- Very high learning cost
- Operationally difficult Tugboat solves these problems by:
- Inheriting Kubernetes design principles
- Not depending on Kubernetes itself
- Providing a lightweight architecture optimized for VMs
- Remaining simple enough for individuals to run
- Scaling to large deployments through a clean, minimal design
- Kubernetes‑compatible API manifests
- Uses familiar structures such as
TypeMetaandObjectMeta - Can use
kubectl
- Uses familiar structures such as
- Declarative cluster powered by etcd
- The apiserver is stateless; etcd is the single source of truth
- Lightweight control plane
- Only the apiserver and scheduler are required
- Direct QEMU execution
- No libvirt; QEMU is invoked directly
- Clear separation of agent and runtime responsibilities
- Similar to Kubernetes’ kubelet/runtime model
- VM images as OCI artifacts
Imagefile -> build -> push to registry -> referenced by Ship- Supports
qcow2andrawboot disk formats
- CNI support
NetworkClass/ClusterNetworkClassbased network configuration- agent publishes plugin readiness to
Node.status.cniPlugins - scheduler filters nodes with
NetworkFit
- RBAC / ServiceAccount
Role/ClusterRole,RoleBinding/ClusterRoleBinding,ServiceAccountAPI (authorization/v1)- Fine-grained verb- and resource-level access control enforced in the apiserver
- Built-in roles:
cluster-admin,admin,edit,view - Opaque bearer tokens generated per ServiceAccount, stored as Secrets
- CustomResourceDefinition (CRD) support
- User-defined resources with Kubernetes-like
CustomResourceDefinition - OpenAPI v3 schema validation, Namespaced/Cluster scope, and optional
/statussubresource
- User-defined resources with Kubernetes-like
- High availability design
- Apiserver can scale horizontally
- Scheduler uses Lease‑based leader election
The current crate and extension boundaries are documented in docs/architecture.md. Contributor-facing resource registration steps are documented in docs/resource-registration.md. VM image build, format, runtime support, and cache layout details are documented in docs/vm-images.md.
| Kubernetes | Tugboat |
|---|---|
| Pod | Ship |
| ReplicaSet | ReplicaSet |
| Deployment | Deployment |
| Node | Node |
| Container image | VM image (OCI) |
| Dockerfile | Imagefile |
| kubelet | agent |
| RuntimeClass | RuntimeClass |
Note:
Fleetis a Tugboat-specific resource for grouping multiple Ship types that share a private network — it has no direct Kubernetes equivalent.
Defines a VM machine type. This is a cluster‑scoped resource.
apiVersion: v1
kind: ShipClass
metadata:
name: lightweight
spec:
cpu:
architecture: x64
cores: 2
memory:
size: 4GiDefines a VM instance. This is a namespaced resource.
apiVersion: v1
kind: Ship
metadata:
namespace: default
name: ship
spec:
image: ghcr.io/sileader/tugboat-vm-images/ubuntu:24.04
shipClass: lightweight
volumes:
- name: data-disk
persistentVolumeClaim:
claimName: data-disk
- name: app-config
configMap:
name: app-config
- name: app-secret
secret:
secretName: app-secretGroups multiple Ship types that share a private network.
This is a namespaced resource (apps/v1).
apiVersion: apps/v1
kind: Fleet
metadata:
namespace: default
name: my-fleet
spec:
networkClassName: my-network-class
components:
- name: frontend
replicas: 2
shipTemplate:
metadata:
labels:
role: frontend
spec:
image: ghcr.io/sileader/tugboat-vm-images/ubuntu:24.04
shipClass: lightweight
- name: backend
replicas: 3
shipTemplate:
metadata:
labels:
role: backend
spec:
image: ghcr.io/sileader/tugboat-vm-images/ubuntu:24.04
shipClass: lightweightTugboat supports user-defined resources via CustomResourceDefinitions, similar to Kubernetes. A CRD declares the resource group, version, kind, plural name, scope, OpenAPI v3 schema, and optional status subresource.
apiVersion: apiextensions/v1
kind: CustomResourceDefinition
metadata:
name: databases.example.com
spec:
group: example.com
names:
plural: databases
singular: database
kind: Database
listKind: DatabaseList
scope: Namespaced
versions:
- name: v1
served: true
storage: true
schema:
openApiV3Schema: |
{"type":"object","required":["spec"],"properties":{"spec":{"type":"object"}}}
subresources:
status: {}See docs/custom-resources.md for the full guide.
Manages a set of identical Ships with rolling-update support.
This is a namespaced resource (apps/v1).
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: default
name: my-deployment
spec:
replicas: 3
selector:
app: my-app
shipTemplate:
metadata:
labels:
app: my-app
spec:
image: ghcr.io/sileader/tugboat-vm-images/ubuntu:24.04
shipClass: lightweightMaintains a stable set of replica Ships.
This is a namespaced resource (apps/v1).
apiVersion: apps/v1
kind: ReplicaSet
metadata:
namespace: default
name: my-replicaset
spec:
replicas: 2
selector:
app: my-app
shipTemplate:
metadata:
labels:
app: my-app
spec:
image: ghcr.io/sileader/tugboat-vm-images/ubuntu:24.04
shipClass: lightweightDeclares the capabilities of the VM runtime on a node (live migration support, hotplug support). This is a cluster‑scoped resource.
apiVersion: v1
kind: RuntimeClass
metadata:
name: standard
spec:
liveMigration: true
hotplug:
cpu:
add: true
remove: false
memory:
add: true
remove: false
nic:
add: true
remove: true
storage:
add: true
remove: trueA Ship can reference a RuntimeClass by name via spec.runtimeClass. The scheduler uses the
RuntimeClassFit plugin to ensure Ships are only placed on nodes whose associated RuntimeClass
satisfies the Ship's requirements (e.g., live migration capability).
CSI-backed volumes can be referenced through spec.volumes[].persistentVolumeClaim. The
legacy volumeClaimRef field is still accepted for backward compatibility.
Current node-side support matrix:
-
BlockvolumeMode -
FilesystemvolumeMode - drivers that require
NodeStageVolume/NodeUnstageVolume -
nodePublishSecretRef/nodeStageSecretRef - agent restart recovery from persisted publish state
- explicit
fs_typeandvolume_attributes -
NodeExpandVolume/ volume expansion - drivers that require controller publish context
-
NodeGetVolumeStats/ CSI volume health + usage surfacing on PV/PVC conditions
Filesystem volumes are exposed to the guest as a 9p share. The mount tag is the Ship
volume name, so both CSI Filesystem claims and projected ConfigMap / Secret
volumes are passed to the guest through the same mechanism.
Control-plane storage support includes PersistentVolume, PersistentVolumeClaim, and StorageClass APIs plus dynamic
CSI provisioning, managed PV cleanup, capacity-aware provisioning/expansion, filesystem claims, and CSI secret /
fsType propagation in tugboat-controller-manager. Node-side support also includes controller-publish-context
handling, live NodeExpandVolume (without Ship recreate) when the driver advertises it, and NodeGetVolumeStats-backed
PV/PVC condition updates for CSI health and usage. The main remaining gap is richer recovery beyond persisted publish state.
Tugboat supports topology-aware scheduling to ensure Ships are placed on nodes that have access to the required
resources, such as storage. This is achieved through well-known labels like topology.tugboat.cloud/region and
topology.tugboat.cloud/zone.
Example manifest for a Ship with node affinity:
apiVersion: v1
kind: Ship
metadata:
name: topology-aware-ship
spec:
image: ghcr.io/sileader/tugboat-vm-images/ubuntu:24.04
shipClass: lightweight
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.tugboat.cloud/zone
operator: In
values:
- zone-aSee topology-aware scheduling for more details.
Volume snapshots allow you to create a point-in-time copy of a volume. These snapshots can then be used to provision new volumes (restore) or to create clones of existing volumes.
Example VolumeSnapshot manifest:
apiVersion: snapshot/v1
kind: VolumeSnapshot
metadata:
name: my-snapshot
namespace: default
spec:
volumeSnapshotClassName: csi-hostpath-snapclass
source:
persistentVolumeClaimName: data-diskSee volume snapshots for more details.
Ship snapshots capture the entire state of a VM, including its memory and the state of its volumes. This allows for full backup and restoration of a VM instance.
Example ShipSnapshot manifest:
apiVersion: snapshot/v1
kind: ShipSnapshot
metadata:
name: my-ship-snapshot
namespace: default
spec:
shipName: my-ship
includeVolumes: trueSee ship snapshots for more details.
Tugboat now exposes observed CNI readiness through Node.status.cniPlugins and
publishes NetworkClass.status.readyNodes / ClusterNetworkClass.status.readyNodes
from tugboat-controller-manager. The scheduler's NetworkFit filter uses that
status to reject nodes that do not advertise the plugins required by a Ship's
requested NetworkClass / ClusterNetworkClass.
The current rollout assumes Flannel itself is installed and managed externally. For a manual multi-node validation flow:
- Install the required CNI binaries (
bridge,loopback,flannel, andportmapwhen port mappings are enabled) on each node under the configured CNI bin directory. - Bring up Flannel externally so each node has the expected runtime state
(by default
/run/flannel/subnet.envand/var/lib/cni/flannel). - Start
tugboat-agenton each node and confirmkubectl get node -o yamlshowsstatus.cniPluginswith the expected readiness. - Apply a
ClusterNetworkClassorNetworkClassusingcniPlugin: flanneland confirm itsstatus.readyNodescontains the nodes that passed the probe. - Create Ships that reference that network class and verify they schedule only to ready nodes before performing cross-node connectivity checks.
Tugboat provides a RBAC system. Access control is enforced in the apiserver for every request.
Detailed documentation:
- RBAC Overview
- Service Account Tokens & Projection
- OIDC Integration
- Aggregated ClusterRoles
- Audit Logging
| Resource | API Group | Scope | Description |
|---|---|---|---|
ServiceAccount |
core/v1 |
Namespaced | Identity for automated processes and controllers |
Role |
authorization/v1 |
Namespaced | Permission rules scoped to a single namespace |
ClusterRole |
authorization/v1 |
Cluster | Permission rules that apply cluster-wide |
RoleBinding |
authorization/v1 |
Namespaced | Bind a Role or ClusterRole to subjects within a namespace |
ClusterRoleBinding |
authorization/v1 |
Cluster | Bind a ClusterRole to subjects cluster-wide |
Supported verbs: get, list, watch, create, update, patch, delete, deletecollection
Subject types: User, Group, ServiceAccount
Permission can be further narrowed to specific resource instances via resourceNames.
| ClusterRole | Description |
|---|---|
cluster-admin |
Full access to all resources |
admin |
Full access within a namespace; cannot modify RBAC or namespace itself |
edit |
Read/write access to most namespaced resources; cannot read Secrets or modify RBAC |
view |
Read-only access to most namespaced resources |
apiVersion: v1
kind: ServiceAccount
metadata:
namespace: default
name: my-service-accountGrants read access to Ships within the default namespace.
apiVersion: authorization/v1
kind: Role
metadata:
namespace: default
name: ship-reader
rules:
- apiGroups: [ "" ]
resources: [ "ships" ]
verbs: [ "get", "list", "watch" ]Grants read access to Nodes cluster-wide.
apiVersion: authorization/v1
kind: ClusterRole
metadata:
name: node-reader
rules:
- apiGroups: [ "" ]
resources: [ "nodes" ]
verbs: [ "get", "list", "watch" ]Binds ship-reader to a user and a ServiceAccount within the default namespace.
apiVersion: authorization/v1
kind: RoleBinding
metadata:
namespace: default
name: ship-reader-binding
roleRef:
apiGroup: authorization
kind: Role
name: ship-reader
subjects:
- kind: User
name: alice
- kind: ServiceAccount
namespace: default
name: my-service-accountGrants the node-reader ClusterRole to an entire group cluster-wide.
apiVersion: authorization/v1
kind: ClusterRoleBinding
metadata:
name: node-reader-binding
roleRef:
apiGroup: authorization
kind: ClusterRole
name: node-reader
subjects:
- kind: Group
name: ops-teamExtends the view role with custom resource permissions.
apiVersion: authorization/v1
kind: ClusterRole
metadata:
name: my-extension-view
labels:
rbac.tugboat.cloud/aggregate-to-view: "true"
rules:
- apiGroups: ["my.example.com"]
resources: ["myresources"]
verbs: ["get", "list", "watch"]apiVersion: authorization/v1
kind: RoleBinding
metadata:
name: oidc-developers-binding
namespace: default
subjects:
- kind: Group
name: oidc:developers
apiGroup: authorization/v1
roleRef:
kind: ClusterRole
name: view
apiGroup: authorization/v1apiVersion: core/v1
kind: Ship
metadata:
name: my-ship
spec:
serviceAccountName: my-sa
automountServiceAccountToken: true- Runtime
- tugboat-qemu-runtime
- tugboat-cloud-hypervisor-runtime
- tugboat-resources (Resource definitions)
- tugboat-resource-store (etcd wrapper for apiserver)
- tugboat‑apiserver
- tugboat-client
- tugboat-cli build (Build a VM Image from a Imagefile)
- fieldSelector and labelSelector
- tugboat‑scheduler
- tugboat‑agent
- Node auto-registration
- Reconcile on Ship Added events
- Networking (CNI, NetworkClass / ClusterNetworkClass)
- Reconcile on Ship Modified events
- Reconcile on Ship Deleted events
- Storage (CSI publish/stage, controller publish context, and live expansion)
- Topology-aware scheduling and snapshot-style workflows
- Secret
- Namespace resource definition and API (
core/v1) - tugboat-controller-manager
- Dynamic CSI volume provisioning
- CSI-backed managed PV cleanup
- ReplicaSet controller (maintaining the prescribed number of Ships)
- Deployment controller (rolling-update management of ReplicaSets)
- Fleet controller
- NamespaceDefaultServiceAccountController (auto-creates
defaultServiceAccount per Namespace) - ServiceAccount token issuance controller (creates
service-account-tokenSecrets)
- Fleet resource definition and API (
apps/v1) - ReplicaSet resource definition and API (
apps/v1) - Deployment resource definition and API (
apps/v1) - ConfigMap
- NetworkClass / ClusterNetworkClass resource definition and API (
core/v1) - PersistentVolume / PersistentVolumeClaim / StorageClass resource definition and API (
core/v1) - Lease resource definition and API (
coordination/v1) - Live migration
- Core migration triggered by
target_node_name - Migration state machine (Pending, Ready, Migrating, Completed, Failed)
- Migration status and conditions reflected on Ship
- Preflight compatibility checks (CPU, shared-storage eligibility, target network capability)
- Reliable recovery and explicit error reporting on migration failure
- Guest/network continuity via deterministic bridge/interface/MAC identity across nodes
- Timeout detection with automatic QEMU cancel (Pending: 2 min, Migrating: 30 min)
- Per-ShipClass configurable QEMU migration parameters (bandwidth, downtime, xbzrle cache, postcopy)
- Scheduler StorageFit plugin rejects nodes for Ships with non-RWX volumes
- Core migration triggered by
- RuntimeClass
- Resource definition and API (
core/v1) -
spec.runtimeClassfield on Ship - Scheduler
RuntimeClassFitplugin (live migration capability check) - Hotplug operations gated by RuntimeClass flags
- Resource definition and API (
- RBAC / ServiceAccount
-
ServiceAccountresource definition and API (core/v1) -
Role/ClusterRoleresource definition and API (authorization/v1) -
RoleBinding/ClusterRoleBindingresource definition and API (authorization/v1) - RBAC authorization enforcement in apiserver
- Built-in roles (
cluster-admin,admin,edit,view) - ServiceAccount token generation and validation
- Opaque bearer token issued via Secret of type
service-account-token - Default ServiceAccount auto-created per namespace
- Signed JWT tokens with audience/expiry
- Opaque bearer token issued via Secret of type
- ServiceAccount token auto-projection into Ships
- OIDC integration for external identity providers
- Aggregated ClusterRoles
- Audit logging
-
- Installer (systemd setup)
-
install-control-plane.sh— installs etcd, apiserver, scheduler, controller-manager as systemd units -
install-worker.sh— installs agent and VM runtime (QEMU or Cloud Hypervisor) as systemd units - TLS / PKI auto-generation (
setup-pki.sh,setup-etcd-pki.sh) - RBAC bootstrap for control-plane ServiceAccount tokens (
bootstrap-rbac.sh) - Flannel CNI support (static subnet.env / vxlan / host-gw via
bootstrap-flannel.sh) - CSI hostpath provisioner installer (
install-csi-hostpath.sh) -
uninstall.sh— stops and removes all Tugboat units, binaries, and configs - Docker-based isolated test environment with scenario scripts (
installer/systemd/test/)
-
- Snapshot
-
VolumeSnapshot,VolumeSnapshotContent, andVolumeSnapshotClassresource definitions and API (snapshot/v1) -
ShipSnapshotresource definition and API (snapshot/v1) - VolumeSnapshot controller for CSI snapshot create/delete and status propagation
- ShipSnapshot volume fanout and agent runtime snapshot reconciliation
- Runtime snapshot create/delete/restore/list support for QEMU and Cloud Hypervisor
- PVC restore and clone via
spec.dataSource
-
- CRD
- Production readiness
- Observability
- Add
/readyz//livezendpoints for control-plane components and node agents - Expose Prometheus-style metrics for apiserver, scheduler, controller-manager, agent, CNI, CSI, and runtimes
- Provide default dashboards and alerting examples for scheduling, reconciliation, storage, networking, runtime, and audit failures
- Add
- Control-plane HA and failure handling
- Add Lease-based leader election or another single-writer mechanism for controller-manager controllers
- Define resync, retry, backoff, and stuck-reconcile recovery behavior for every controller and agent operation
- Document load-balancer and multi-apiserver deployment patterns with readiness-aware routing
- Security hardening
- Support certificate and ServiceAccount signing-key rotation without cluster downtime
- Add encryption-at-rest support for Secrets and other sensitive resources stored in etcd
- Provide production config profiles that disable insecure HTTP, insecure etcd, anonymous auth, and development-only defaults
- Finish audit log size-based rotation and age-based retention; surface dropped audit events as metrics
- Backup and disaster recovery
- Document and test etcd snapshot/restore procedures
- Add scheduled backup/retention workflows for ShipSnapshot and VolumeSnapshot resources
- Validate restore drills for control-plane state, VM runtime state, and CSI-backed persistent volumes
- Upgrade and release operations
- Define rolling-upgrade procedures and compatibility guarantees across apiserver, controllers, scheduler, agents, and runtimes
- Add API version migration/conversion strategy for future breaking resource changes
- Publish signed release artifacts, SBOMs, and reproducible install inputs
- Production networking and storage validation
- Manage or fully document multi-node CNI lifecycle instead of relying on externally managed Flannel state
- Maintain a tested CSI driver compatibility matrix covering topology, expansion, snapshots, clones, health, and recovery
- Add or document in-place attach/detach behavior for volumes and filesystem shares, including virtiofs migration from 9p
- Installer and operational tooling
- Add idempotent upgrade, rollback, and config-drift checks to the systemd installer
- Add host preflight checks for KVM, QEMU/Cloud Hypervisor, CNI binaries, CSI sockets, kernel modules, disk space, and time sync
- Extend isolated installer scenarios into a release-blocking multi-node smoke suite
- Observability
The installer/systemd/ directory contains shell scripts that install and configure
Tugboat on a Debian/Ubuntu host using systemd. Root access and a working
apt-get are required.
sudo installer/systemd/install-control-plane.sh \
--build \
--apiserver-host control-plane.example.com \
--apiserver-ip 192.168.0.1This installs etcd, tugboat-apiserver, tugboat-scheduler, and
tugboat-controller-manager as systemd services. By default the API server
listens on 0.0.0.0:8443 with TLS (auto-generated CA + server certificate).
Pass --insecure to use plain HTTP on port 8080 instead.
Use --use-prebuilt --bin-dir <path> in place of --build to supply
pre-compiled binaries.
sudo installer/systemd/install-worker.sh \
--use-prebuilt --bin-dir /path/to/bins \
--apiserver-url https://192.168.0.1:8443 \
--ca-cert /etc/tugboat/pki/ca.crt \
--service-account-token /path/to/agent-tokenThis installs tugboat-agent and the selected VM runtime (--runtime qemu
or --runtime cloud-hypervisor). CNI plugins are installed automatically.
Flannel mode is controlled by --flannel-mode static|vxlan|host-gw (default:
static, which writes /run/flannel/subnet.env without running flanneld).
sudo installer/systemd/uninstall.sh --control-plane # control-plane node
sudo installer/systemd/uninstall.sh --worker # worker node
sudo installer/systemd/uninstall.sh --control-plane --worker --purge # full cleanupThe installer/systemd/test/ directory provides a Docker-based isolation
environment (Ubuntu 24.04 with systemd as PID 1). Run all scenarios with:
installer/systemd/test/run-tests.shTugboat is still in an early stage, and contributions of any kind are welcome.
Apache License 2.0 See LICENSE for details.