Migrating from Chef
Vigo and Chef share the pull-based model and converge-on-interval pattern. The biggest shift is from Ruby DSL to plain YAML, and from Chef's 15-level attribute precedence to three levels. If you've managed infrastructure with Chef, Vigo's resource model will be instantly recognizable.
Architecture Differences
Both Chef and Vigo use a pull-based agent that contacts a server on a regular interval, receives desired state, and converges locally. Chef compiles Ruby code into a resource collection; Vigo interprets YAML directly — there's no compilation step.
Chef's infrastructure includes a Chef Server (or Chef Automate), client nodes running chef-client (Ruby), and often a Supermarket for cookbook distribution. Vigo has a Go server (vigosrv), a Rust agent (vigo), and a CLI (vigocli). No Ruby runtime, no package manager, no external services.
Chef stores node objects and search indexes on the server (PostgreSQL + Elasticsearch in Chef Server). Vigo uses an in-memory FleetIndex backed by SQLite — no external database.
Chef's attribute system has 15 precedence levels (default, force_default, normal, override, force_override, automatic — each at role, environment, cookbook, and recipe levels). Vigo has three levels: module defaults, role vars, node vars.
Concept Mapping
| Chef Concept | Vigo Equivalent | Notes |
|---|---|---|
| Cookbook | Module (.vgo file) |
Single YAML file replaces the cookbook directory tree |
| Recipe | Module resources | Resources listed in the module — no separate recipe files |
| Role | Role | Named list of modules — same concept |
| Run list | Module list in nodes.vgo |
Roles and modules assigned to matching envoys |
| Node object | FleetIndex entry + Traits | In-memory, not stored server-side as a document |
| Ohai | Traits | System facts collected by the agent |
| Attributes (default) | Module vars: |
Default values in the module |
| Attributes (override) | Node vars: in nodes.vgo |
Node-level overrides |
| Data bag | vars_from: |
External YAML data loaded at check-in |
| Chef Vault | secret: prefix |
Resolved server-side, never in plaintext |
| Environment | environment_overrides: in nodes.vgo |
Environment-specific variable overrides |
notifies |
notify |
Trigger downstream resource on change |
subscribes |
subscribes |
Same keyword, same semantics |
only_if / not_if |
when: |
Resource conditionals |
include_recipe |
Module dependencies | Modules listed in roles or depends_on |
knife |
vigocli |
CLI for server administration |
| Chef Supermarket | No equivalent | Modules are plain YAML files — no registry |
| Policyfile | nodes.vgo |
Pin module versions at the node level |
chef-client |
vigo agent |
Rust binary, LMDB state, offline convergence |
chef-server |
vigosrv |
Go binary, gRPC + REST + Web UI |
| ERB template | Go template in content: |
Templates only in content: attribute |
knife bootstrap |
vigocli tokens + bootstrap script |
One-time token enrollment |
Resource Type Mapping
| Chef Resource | Vigo Executor | Notes |
|---|---|---|
package |
package |
Auto-detects package manager |
template / cookbook_file |
file |
content: for templates, sourcepath: for static files |
service |
service |
state: running/stopped, enabled: true/false |
execute / bash |
exec |
command:, onlyif:, unless: |
user |
user |
username:, uid:, shell:, groups: |
group |
group |
groupname:, gid:, members: |
cron |
cron |
command:, minute:, hour:, etc. |
apt_repository / yum_repository |
repository |
Unified across distros |
remote_file (archive) |
source_package |
url:, target_path:, checksum: (tarballs/zips) |
remote_file + dpkg_package/rpm_package |
nonrepo_package |
Install .deb/.rpm/.pkg/.msi from URLs |
mount |
mount |
device:, mountpoint:, fstype: |
sysctl |
sysctl |
key:, value: |
hostname |
hostname |
hostname: |
file (line) |
file_line |
path:, line:, match: |
Walkthrough: Converting an Nginx Cookbook
Chef Version
cookbooks/nginx/recipes/default.rb:
package 'nginx' do
action :install
end
template '/etc/nginx/nginx.conf' do
source 'nginx.conf.erb'
owner 'root'
group 'root'
mode '0644'
variables(
worker_processes: node['nginx']['worker_processes'],
worker_connections: node['nginx']['worker_connections'],
server_name: node['nginx']['server_name'],
root: node['nginx']['root']
)
notifies :restart, 'service[nginx]'
end
service 'nginx' do
action [:enable, :start]
end
cookbooks/nginx/attributes/default.rb:
default['nginx']['worker_processes'] = 'auto'
default['nginx']['worker_connections'] = 1024
default['nginx']['server_name'] = 'localhost'
default['nginx']['root'] = '/var/www/html'
cookbooks/nginx/templates/default/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
- No Ruby. Chef's entire stack runs on Ruby — cookbooks, recipes, attributes, templates. Vigo is pure YAML with Go templates in
content:only. - One file per module. A Chef cookbook is a directory tree (
recipes/,templates/,attributes/,metadata.rb, etc.). A Vigo module is a single.vgofile. - No attribute levels. Chef's
default/normal/override/force_override/automaticat cookbook/role/environment/node levels gives 15 precedence tiers. Vigo has three: module defaults → role vars → node vars. - Templates inline or in
templates/. Small templates go directly incontent:. For larger files, usesource: templates/nginx.conf.tmpl— the server reads the file from thetemplates/directory and renders it with the same Go template engine. Notemplates/default/subdirectory convention.
Variable and Data Hierarchy
Chef attributes have 15 precedence levels. In practice, most teams use default and override. Vigo maps this cleanly:
| Chef Level | Vigo Equivalent |
|---|---|
default (cookbook) |
Module vars: |
default (role) |
Role vars: |
override (node/environment) |
Node vars: / environment_overrides: |
automatic (Ohai) |
Traits (accessed via .Traits in templates, or when:) |
| Data bags | vars_from: (loads external YAML) |
The environment_overrides: key provides environment-specific overrides without separate environment objects:
envoys:
- match: "*.web.example.com"
roles: [webserver]
vars:
nginx_server_name: "example.com"
environment_overrides:
staging:
nginx_server_name: "staging.example.com"
Templates
Chef uses ERB. Vigo uses Go templates.
| Chef ERB | Vigo Go Template |
|---|---|
<%= @variable %> |
{{ .Vars.variable }} |
<%= node['attr'] %> |
{{ .Vars.attr }} |
| <%= node['fqdn'] %> | {{ .Traits.identity.fqdn }} |
| <% if condition %> | {{ if .Vars.condition }} |
| <% @items.each do \|i\| %> | {{ range .Vars.items }} |
Critical restriction: Go templates work only in content: attributes. You cannot use {{ }} in package:, target_path:, command:, or any other attribute.
Conditionals and Targeting
Chef conditionals:
package 'nginx' do
action :install
only_if { node['platform_family'] == 'debian' }
end
Vigo equivalent:
- name: nginx-package
type: package
package: nginx
state: present
when: "os_family('debian')"
Chef's only_if/not_if guards run arbitrary Ruby. Vigo's when: uses a fixed expression syntax with 15 builtins and boolean operators.
Secrets
Chef uses data bags (optionally encrypted) or Chef Vault:
db_password = data_bag_item('secrets', 'database')['password']
Vigo uses the secret: prefix:
content: "secret:vigo/database/password"
The secrets provider resolves references at bundle compile time. No Ruby code, no key management ceremony.
Common Gotchas
-
No Ruby. You cannot run arbitrary code during convergence. If Chef recipes contain procedural Ruby logic beyond resource declarations, you'll need to decompose it into declarative resources with
when:conditionals. -
No compile phase. Chef has a two-phase model (compile then converge). Vigo has no compile phase — resources are interpreted from YAML directly. There's no equivalent of
lazy {}evaluation. -
Templates only in
content:. Chef lets you use ERB in any string and template helper methods everywhere. Vigo restricts Go templates tocontent:. Usewhen:and separate resources for conditional logic. -
Flat variable namespace. Chef attributes are nested hashes (
node['nginx']['port']). Vigo vars are flat strings (nginx_port). Prefix with the module name to avoid collisions. -
No search. Chef Server's search API (
search(:node, 'role:webserver')) has no direct equivalent. For cross-node coordination, usevars_from:or the task/query system. -
No
run_listmanipulation. Chef can dynamically include recipes. Vigo's module list is static — defined innodes.vgorole assignments. Usewhen:to conditionally skip entire modules. -
No Berkshelf/Policyfile dependency resolution. Modules don't declare dependencies on other modules. Include all required modules in the role definition.
-
First-match-wins node classification. Chef can merge multiple roles and environments. Vigo's
nodes.vgouses first-match-wins — only the first matching glob applies.
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.