Workloads
Workloads are the core abstraction in Voltis for defining and deploying sets of services, packages, and jobs to edge nodes. A workload is essentially a portable bundle of configuration and tasks that can be pushed to the daemon, installed, and activated. This allows for reproducible deployments across devices, with automatic handling of installation order, state management, and reconciliation.
Workloads are designed to be idempotent, meaning they can be applied multiple times without side effects, thanks to status checks in taskfiles. They support both systemd-managed services and simple executables, making Voltis versatile for various edge scenarios.
Workloads that are built and pushed to the Voltis daemon can be activated at any point, however Voltis handles only one active workload at a time. This reduces any conflicts between workloads and each workload is fully uninstalled before installing a new workload.
What is a Workload?
A workload encapsulates:
- Services: Systemd units or executables with install/uninstall and state management tasks.
- Packages: Dependencies (e.g., Debian packages) installed via taskfiles.
- Jobs: One-time or continuous tasks executed during deployment.
- Configuration: Ordering and desired states defined in
voltis.toml.
Once pushed to the daemon, workloads are stored in the SQLite database with a unique digest (SHA-256 of contents). Only one workload can be active at a time; activating a new one deactivates and uninstalls the previous.
Creating a Workload
Workloads are created as directories following a specific structure. All files are bundled into a gzipped tarball via the voltis workload buildfile command for transport.
Directory Structure
my-workload/
├── voltis.toml # Required: Workload configuration
├── service1.voltis.taskfile.yml # Required per service: Tasks for install/uninstall/state
├── package1.voltis.taskfile.yml # Required per package: For package-only components
├── job1.voltis.taskfile.yml # Required per job: For job-only components
└── extras/ # Optional: Custom files for systemd units
├── service1.service- voltis.toml: Defines metadata, service order, and states.
- *.voltis.taskfile.yml: Taskfiles for each component (services/packages/jobs). Named after the component (e.g.,
docker.voltis.taskfile.yml). - extras/: Arbitrary files copied to the target system during install (e.g., systemd units to
/etc/systemd/system/, scripts to/usr/local/bin/).
voltis.toml Configuration
This TOML file is mandatory and must be in the workload root. It specifies the workload name and configures components.
version = "1" # Required: Current schema version
workload = "my-edge-setup" # Required: Unique identifier (used for push/active)
# Installation sequence: Components installed in this order
installOrder = ["docker", "k3s", "gitea"]
# Uninstallation sequence: Reverse order for clean removal
uninstallOrder = ["gitea", "k3s", "docker"]
stateApplyInterval = 5 # interval for component state apply
stateUpdateInterval = 3 # interval for component state update (read from system)
workloadUpdateInterval = 30 # workload update interval for switching active workload
workloadReconcile = true # reconcile boolean affecting entire workload
# Service configurations
[services.docker]
state = "active" # Desired state: "active" (start) or "inactive" (install only)
systemdUnitName = "docker" # Matches the systemd service name
[services.k3s]
state = "active"
systemdUnitName = "k3s"
[services.gitea]
state = "inactive"
systemdUnitName = "gitea"
# Package-only components (install and reconcile by default)
[packages.nginx]
# Job-only components
[jobs.apt]
autoReconcile = false- version: Always “1” for current implementations.
- workload: Workload name for identification; must be unique per daemon.
- installOrder/uninstallOrder: Arrays of component names (must match taskfile basenames). Ensures dependencies are handled correctly (e.g., install Docker before k3s).
- stateApplyInterval: Interval in seconds for how often new component should be applied to components
- stateUpdateInterval: Interval in seconds for how often new component states should be read from the system
- workloadUpdateInterval: Interval in seconds for how often new workloads get applied to the daemon if switching
- workloadReconcile: Toggle for reconcile affecting entire workload. If
falseafter the first install no reconciles will occur; this takes precedence over the autoReconcile option forjobs. Reconcile can also be toggled via the CLI Guide - [services.<name>]: For services with runtime state.
state: “active” enables and starts the service; “inactive” installs but doesn’t start.systemdUnitName: Exact name of the systemd unit (required for active state).
- [packages.<name>]: For install-only components. No direct configuration settings required.
- [jobs.<name>]: For components that run independently without an executable.
autoReconcile: If enabled the job will run every time on thestateApplyInterval; otherwise it will run once on install.
If a component is listed but lacks a taskfile, installation will fail. The listed values for intervals are the default.
Taskfiles
Taskfiles define the actual operations using the Task syntax. Voltis executes specific tasks during lifecycle events:
action.install: Run on workload push/activation (if not installed).action.uninstall: Run on reset/deactivation.state.active: Run when activating with “active” status (starts service).state.inactive: Run when deactivating or setting to “inactive”.
Taskfiles support shell commands, status checks for idempotency, and preconditions.
Example: Docker Service Taskfile (docker.voltis.taskfile.yml)
version: "3"
tasks:
action.install:
desc: "Install Docker CE on Ubuntu/Debian"
status:
- which docker # Idempotency: Skip if already installed
cmds:
- apt update -qq
- apt install -y ca-certificates curl gnupg lsb-release
- mkdir -p /etc/apt/keyrings
- curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
- echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
- apt update -qq
- apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
action.uninstall:
desc: "Uninstall Docker CE"
status:
- "! which docker" # Ensure removed
cmds:
- apt remove -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
- apt autoremove -y
- rm -rf /etc/apt/keyrings/docker.gpg /etc/apt/sources.list.d/docker.list
state.active:
desc: "Start and enable Docker service"
preconditions:
- which docker
- systemctl is-enabled --quiet docker || systemctl enable docker
cmds:
- systemctl start docker
- systemctl status docker --no-pager -l
state.inactive:
desc: "Stop Docker service"
preconditions:
- which docker
cmds:
- systemctl stop docker
- systemctl disable docker- status: Array of shell commands; if any succeed, the task is considered “up-to-date” and skipped.
- cmds: Array of shell commands executed sequentially.
- preconditions: Checks before running (fail if not met).
- desc: Human-readable description (logged during execution).
For packages/jobs, omit state.* as they are not run via systemd.
Custom Files in extras/
During action.install, Voltis copies files from extras/ to the target system:
extras/*.service→/etc/systemd/system/- Other files: Placed in a workload-specific directory (e.g.,
/opt/voltis/workloads/my-workload/), with paths configurable in taskfiles.
Example: Custom Gitea systemd unit in extras/gitea.service:
[Unit]
Description=Gitea (Git Service)
After=syslog.target
After=network.target
[Service]
Type=simple
User=git
Group=git
WorkingDirectory=/var/lib/gitea/
ExecStart=/usr/local/bin/gitea web
Restart=always
Environment=USER=git HOME=/home/git GITEA_WORK_DIR=/var/lib/gitea
[Install]
WantedBy=multi-user.targetTaskfile can then run systemctl daemon-reload in state.active.
Building a Workload
Use the CLI to bundle the directory:
voltis workload buildfile ./my-workload --output my-workload.tar.gz- Validates presence of
voltis.toml. - Includes all files recursively (excludes
.git/, hidden files unless specified). - Compresses with gzip for efficient transfer.
- Output: Gzipped tarball ready for push.
To inspect:
tar -tzf my-workload.tar.gz | head -20Deploying Workloads
See Usage Guide for CLI commands (push, active, reset).
Lifecycle
- Push: Upload tarball; extracts to daemon repository.
- Activate: Sets as active workload. Reconciles the workload and performs an idempotent install.
- Reconciliation: Daemon polls every 3s by default; ensures current state matches desired.
- Reset: Runs
state.inactivethenaction.uninstall; removes active workload and components.
Best Practices
- Idempotency: Always use
statuschecks to avoid re-installing. - Dependencies: Order components correctly in
voltis.toml(e.g., runtime before apps). - Error Handling: Taskfiles should fail gracefully; use
|| truefor non-critical cmds. - Testing: Build and push to a test node; monitor logs with
journalctl -u voltis -f --all. - Size Optimization: Keep workloads lean; separate logically into services, packages, jobs.
Builtin Workloads
Voltis includes pre-built workloads in builtin_tasks/ for common setups:
- cluster: Install a k3s cluster with docker runtime and several useful kubernetes services.
- ollama: Local AI model server.
- field-assist-ai: Custom AI application to test GPU enabled workloads.
Clone and customize these as templates. Example: Build the Cluster workload:
voltis workload buildfile builtin_tasks/cluster --output cluster.voltis.tar.gz
voltis workload push cluster.voltis.tar.gz --name cluster --status activeAdvanced Topics
Multi-Platform Support
Taskfiles can detect OS/arch:
cmds:
- if [ "$(uname -s)" = "Linux" ]; then apt install ...; else brew install ...; fiRollback
To revert: Push an older workload version and activate it. The daemon handles uninstall of the previous active one.
For issues, see Troubleshooting.
Next: Core Concepts