Migrating from Puppet
Vigo is the most conceptually similar tool to Puppet — both are pull-based, compile a desired-state catalog on the server, and apply idempotent resources on the agent. If you think in Puppet, Vigo will feel familiar.
Architecture Differences
Both Puppet and Vigo follow a pull model: an agent contacts a central server, receives desired state, and converges locally. The server compiles a catalog (Puppet) or bundle (Vigo) based on the node's identity and facts/traits.
Puppet uses its own DSL for manifests and ERB/EPP for templates. Vigo uses plain YAML for everything and Go templates for content: attributes only. There is no compilation step — the YAML is the config.
Puppet stores catalog state in PuppetDB and queries it for exported resources and reporting. Vigo keeps an in-memory FleetIndex (rehydrated from SQLite on startup) and never touches the database on the check-in hot path.
Puppet's agent runs on a 30-minute interval by default. Vigo's agent interval is configurable and supports compiled promises with offline convergence — the agent caches its last bundle and continues applying it when the server is unreachable.
Concept Mapping
| Puppet Concept | Vigo Equivalent | Notes |
|---|---|---|
Manifest (.pp file) |
Module (.vgo file) |
YAML instead of Puppet DSL |
| Class / Defined Type | Module | One module = one .vgo file with resources |
| Profile | Module | Same level of abstraction |
| Role | Role | Named list of modules |
site.pp / ENC |
nodes.vgo |
First-match-wins glob patterns |
| Hiera | vars / environment_overrides |
3 levels: module defaults → role vars → node vars |
| Facter fact | Trait | Collected automatically, available in when: and templates |
| Catalog | Bundle | Server-compiled desired state sent to agent |
| Resource | Resource | Same concept — idempotent, declarative |
require / before |
depends_on / before |
DAG ordering |
notify |
notify |
Trigger downstream resource re-application |
subscribe |
subscribes |
Pull-based trigger |
ensure => absent |
state: absent |
Same semantics |
| Puppet Forge module | Module .vgo file |
No package manager — just YAML files |
| PuppetDB | FleetIndex + SQLite | In-memory index, no external database |
| hiera-eyaml | secret: prefix |
Resolved at bundle compile time |
| ERB template | Go template in content: |
Templates only in content: — never in target_path:, package:, etc. |
environments/ |
environment_overrides in nodes.vgo |
Environment-specific overrides, not directory-based |
| Puppet agent | vigo agent | Rust binary, LMDB state store |
| Puppet server | vigosrv | Go binary, gRPC + REST |
puppet cert |
vigocli tokens |
One-time token enrollment instead of certificate signing |
Resource Type Mapping
| Puppet Resource | Vigo Executor | Notes |
|---|---|---|
package |
package |
Auto-detects package manager |
file |
file |
Go templates in content:, or sourcepath: for copies |
service |
service |
state: running/stopped, enabled: true/false |
exec |
exec |
command:, onlyif:, unless: |
user |
user |
username:, uid:, shell:, groups: |
group |
group |
groupname:, gid:, members: |
cron |
cron |
command:, minute:, hour:, etc. |
file_line |
file_line |
path:, line:, match: |
yumrepo / apt::source |
repository |
Unified across distros |
archive |
source_package |
url:, target_path:, checksum: (tarballs/zips) |
package (with source) |
nonrepo_package |
Install .deb/.rpm/.pkg/.msi from URLs |
mount |
mount |
device:, mountpoint:, fstype: |
sysctl |
sysctl |
key:, value: |
host |
host |
hostname:, ip: |
Walkthrough: Converting an Nginx Module
Puppet Version
class nginx (
String $worker_processes = 'auto',
Integer $worker_connections = 1024,
String $server_name = 'localhost',
String $root = '/var/www/html',
) {
package { 'nginx':
ensure => installed,
}
file { '/etc/nginx/nginx.conf':
ensure => file,
owner => 'root',
group => 'root',
mode => '0644',
content => template('nginx/nginx.conf.erb'),
notify => Service['nginx'],
require => Package['nginx'],
}
service { 'nginx':
ensure => running,
enable => true,
}
}
With templates/nginx.conf.erb:
worker_processes <%= @worker_processes %>;
events {
worker_connections <%= @worker_connections %>;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name <%= @server_name %>;
root <%= @root %>;
location / {
try_files $uri $uri/ =404;
}
}
}
Vigo Version
stockpile/modules/nginx.vgo:
name: nginx
vars:
nginx_worker_processes: auto
nginx_worker_connections: 1024
nginx_server_name: localhost
nginx_root: /var/www/html
resources:
- name: nginx-package
type: package
package: nginx
state: present
- name: nginx-config
type: file
target_path: /etc/nginx/nginx.conf
owner: root
group: root
mode: "0644"
content: |
worker_processes {{ .Vars.nginx_worker_processes }};
events {
worker_connections {{ .Vars.nginx_worker_connections }};
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name {{ .Vars.nginx_server_name }};
root {{ .Vars.nginx_root }};
location / {
try_files $uri $uri/ =404;
}
}
}
notify:
- nginx-service
- name: nginx-service
type: service
service: nginx
state: running
enabled: true
stockpile/roles/webserver.vgo:
name: webserver
modules:
- nginx
stockpile/envoys/nodes.vgo:
envoys:
- match: "*.web.example.com"
environment: production
roles: [webserver]
vars:
nginx_worker_processes: "4"
nginx_worker_connections: "4096"
nginx_server_name: "example.com"
nginx_root: /var/www/example.com
Key Differences
- Templates inline or in
templates/. Small templates go inline incontent:. Larger files usesource: templates/nginx.conf.tmpl— the server reads and renders the file from thetemplates/directory. No ERB — Go template syntax only. - No
require =>needed for the package → config ordering. Vigo usesnotify:from config to service, and the resource DAG handles the rest. You can add explicitdepends_on: [nginx-package]if you prefer. - Variables are flat. Puppet class parameters become
vars:entries. No scope hierarchy — module defaults, then node-level overrides. That's it. - No catalog compilation. The YAML is interpreted directly. No parser, no AST, no compilation errors to debug.
Variable and Data Hierarchy
Puppet's Hiera provides up to 10+ hierarchy levels (common.yaml → OS family → environment → datacenter → FQDN). Vigo deliberately has three:
- Module defaults —
vars:in the module.vgofile - Role vars —
vars:in the role.vgofile (if needed) - Node vars —
vars:in thenodes.vgoentry
Node vars override role vars, which override module defaults. That's the entire hierarchy. For environment-specific values, use environment_overrides::
envoys:
- match: "*.web.example.com"
roles: [webserver]
vars:
nginx_server_name: "example.com"
environment_overrides:
staging:
nginx_server_name: "staging.example.com"
For external data, vars_from: loads values from a YAML file at check-in time.
Templates
Puppet uses ERB (or EPP). Vigo uses Go templates. Key syntax differences:
| Puppet ERB | Vigo Go Template |
|---|---|
<%= @variable %> |
{{ .Vars.variable }} |
<%= scope['module::param'] %> |
{{ .Vars.param }} |
<%= @fqdn %> |
{{ .Traits.identity.fqdn }} |
<% if @condition %> |
{{ if .Vars.condition }} |
| `<% @items.each do | item |
<%= @variable.downcase %> |
{{ lower .Vars.variable }} |
Critical restriction: Templates are only allowed in content: attributes. You cannot template target_path:, package:, service:, or any other attribute. Use when: expressions for conditional logic and separate resources for OS-specific packages.
Conditionals and Targeting
Puppet conditionals:
if $facts['os']['family'] == 'Debian' {
package { 'nginx': ensure => installed }
} elsif $facts['os']['family'] == 'RedHat' {
package { 'nginx': ensure => installed, name => 'nginx' }
}
Vigo equivalent using when::
resources:
- name: nginx-package-debian
type: package
package: nginx
state: present
when: "os_family('debian')"
- name: nginx-package-redhat
type: package
package: nginx
state: present
when: "os_family('redhat')"
Vigo's when: expressions support 15 builtin functions (os_family(), is_container, has_trait(), trait_value(), etc.) with boolean operators (&&, ||, !).
Secrets
Puppet uses hiera-eyaml to encrypt values in Hiera YAML files. Vigo uses the secret: prefix:
# Puppet (hiera-eyaml)
nginx::ssl_key: ENC[PKCS7,MIIBe...]
# Vigo
content: "secret:vigo/nginx/ssl_key"
The secrets provider resolves secret: references at bundle compile time. The default local backend maps paths to AES-256-GCM encrypted files. Secrets never appear in plaintext in configs, logs, or gRPC payloads.
Common Gotchas
-
No auto-require. Puppet automatically orders
PackagebeforeFilebeforeService. Vigo requires explicitdepends_on:ornotify:/subscribes:for ordering. Without them, resources may execute in any order. -
Templates only in
content:. Puppet lets you template any attribute value. Vigo restricts Go templates to thecontent:attribute. For OS-conditional packages, usewhen:with separate resources — don't try to template thepackage:field. -
No exported resources. Puppet's
@@exported resources and PuppetDB collectors don't have a direct equivalent. Usevars_from:to share data between nodes, or coordinate through task/workflow orchestration. -
Flat variable namespace. There's no
$module::paramscoping. All vars are flat strings in a single namespace. Prefix var names with the module name (e.g.,nginx_worker_processes) to avoid collisions. -
No
ensure => absentfor everything. Not all executors supportstate: absent. Check the executor documentation for available states. -
First-match-wins. Puppet's node classification can merge multiple matching entries. Vigo's
nodes.vgouses first-match-wins — only the first matching glob applies. Order your entries from most specific to least specific. -
No Puppet Forge. There's no package manager for modules. Modules are plain
.vgoYAML files in your config directory. Copy, adapt, and version-control them directly. -
Exec refresh. Puppet's
execresources userefreshonly => trueto only run on notification. Vigo useswhen: "changed"— the resource is skipped on normal check-ins and only runs when triggered bynotify,subscribes, orwatch_secret.
Related
- Config Format — Modules, roles, nodes.vgo, vars
- Resource DAG — depends_on, notify, subscribes
- Templates — Go template syntax and restrictions
- When Expressions — Conditional expressions and builtins
- Secrets — Secret providers and the secret: prefix
- Executors — All resource executors
- Nginx Module Example — Full nginx walkthrough
- First Module — Writing your first module
Confidential -- Alexander4, LLC. Not for redistribution. See documentation-license.