Understanding the Migration: From Ansible to Chef
Migrating from Ansible to Chef means transitioning your infrastructure-as-code practices from an agentless, YAML-based, push-oriented automation tool to an agent-based, Ruby-domain-specific-language (DSL) system that operates primarily in a pull model. In Ansible, you write playbooks that connect to nodes over SSH and execute tasks sequentially. In Chef, you write recipes and cookbooks that define the desired end-state of a system, and Chef clients (agents) periodically converge the node to that state by pulling policies from a Chef Infra Server.
This shift is not just about changing syntax; it involves adopting a different operational philosophy centered on continuous convergence, versioned policies, and a centralized infrastructure management model. The goal of this guide is to provide a complete, practical, step-by-step tutorial to help you move your existing Ansible automation estate to Chef smoothly and safely.
Why Migrate from Ansible to Chef?
Choosing Chef over Ansible often stems from specific organizational needs. Common drivers include:
- Scalable pull-based architecture β Chef agents automatically converge nodes without requiring an SSH connection from a central control machine, making it ideal for large, dynamic environments.
- Convergence model β Chef continuously enforces the desired state, remediating drift over time, which suits compliance-heavy environments (e.g., PCI-DSS, HIPAA).
- Built-in policy management β Chef Infra Server provides a central store for cookbooks, roles, environments, and node attributes, enabling fine-grained access control and auditing.
- Extensive ecosystem β Chef has a massive library of community cookbooks on the Chef Supermarket, plus deep integrations with testing frameworks (Test Kitchen, InSpec) and compliance scanning.
- Multi-platform consistency β Chef handles Windows, Linux, and cloud platforms uniformly, with resources for packages, services, registry keys, and more.
Step-by-Step Migration Guide
π Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →1. Assess Your Current Ansible Setup
Before writing any Chef code, catalog your existing Ansible inventory. Identify:
- All playbooks and roles you rely on.
- Custom modules or filters.
- Inventory files, group/host variables.
- Vault-encrypted secrets.
- Dynamic inventory scripts or cloud plugins.
- Typical execution patterns (e.g., ad-hoc commands, playbook runs).
Document the purpose of each playbook and role. This assessment will directly feed into the conversion process and help you prioritize which parts to migrate first.
2. Install and Configure Chef Infrastructure
Set up a Chef Infra Server and a development workstation. You can use a hosted Chef server or install Chef Infra Server on-premises. The workstation is where you will author cookbooks, upload them, and bootstrap nodes.
Example commands to set up a Chef Workstation (using the Chef Workstation package):
# Install Chef Workstation (includes knife, chef, test-kitchen, etc.)
curl -L https://omnitruck.chef.io/install.sh | sudo bash -s -- -P chef-workstation
# Or use the platform-specific installer
Configure knife (the CLI tool) to communicate with your Chef Infra Server:
# knife.rb configuration in ~/.chef/
current_dir = File.dirname(__FILE__)
node_name "your-username"
client_key "#{current_dir}/your-user.pem"
chef_server_url "https://chef-server.example.com/organizations/your-org"
cookbook_path ["#{current_dir}/../cookbooks"]
Verify connectivity:
knife status
# or
knife list /roles
3. Map Ansible Concepts to Chef Equivalents
Understanding the conceptual mapping accelerates the translation. Here are the key correspondences:
- Playbook β A cookbook (a collection of recipes, attributes, templates, and other components) or a role (which can combine multiple cookbooks).
- Task β A recipe (a Ruby file containing resources that define the desired state).
- Module β A Chef resource (e.g.,
package,service,file,template,execute). - Variable β Chef attributes (node attributes defined in attribute files, roles, environments, or node objects).
- Jinja2 template β Chef template (ERB β Embedded Ruby).
- Handler/Notify β Chef notifications (
notifies :restartfrom a resource to a service). - Inventory β Chef Infra Server node objects (managed via knife or policy-based bootstrapping).
- Role β Chef role (a JSON or Ruby DSL file that specifies run-lists and attribute overrides).
- Dynamic inventory β Chef Infra Server search indexes, cloud-specific knife plugins, or Policyfiles with cookbook-based targeting.
4. Convert Ansible Playbooks to Chef Recipes
Begin with a simple, well-understood playbook. For instance, an Ansible playbook that installs Nginx, ensures it is running, and deploys a custom configuration:
# ansible playbook: install-nginx.yml
---
- name: Install and configure Nginx
hosts: webservers
become: yes
tasks:
- name: Ensure Nginx is installed
ansible.builtin.apt:
name: nginx
state: present
when: ansible_os_family == "Debian"
- name: Ensure Nginx is running
ansible.builtin.service:
name: nginx
state: started
enabled: yes
- name: Deploy Nginx configuration template
ansible.builtin.template:
src: default.conf.j2
dest: /etc/nginx/sites-available/default
notify: restart nginx
handlers:
- name: restart nginx
ansible.builtin.service:
name: nginx
state: restarted
The equivalent Chef recipe inside a cookbook would look like this. Create a cookbook named nginx_cookbook with a recipe default.rb:
# Chef recipe: cookbooks/nginx_cookbook/recipes/default.rb
package 'nginx' do
action :install
end
service 'nginx' do
action [ :enable, :start ]
end
template '/etc/nginx/sites-available/default' do
source 'default.conf.erb'
owner 'root'
group 'root'
mode '0644'
notifies :restart, 'service[nginx]', :delayed
end
Notice the Chef recipe is purely declarative β it describes the desired state. The notifies line triggers a service restart when the template changes, similar to Ansible handlers. Chef will converge the node to this state each time it runs.
5. Translate Variables and Templates
Ansible variables can exist at multiple scopes (group_vars, host_vars, role defaults). In Chef, attributes serve this purpose. Define default attributes in an attributes/default.rb file inside the cookbook:
# cookbooks/nginx_cookbook/attributes/default.rb
default['nginx']['listen_port'] = 80
default['nginx']['server_name'] = 'localhost'
default['nginx']['root'] = '/var/www/html'
Access these attributes in recipes using node['nginx']['listen_port']. For templates, Ansible uses Jinja2; Chef uses Embedded Ruby (ERB). Convert your Jinja2 template to ERB:
Ansible template (Jinja2):
# templates/default.conf.j2
server {
listen {{ listen_port }};
server_name {{ server_name }};
root {{ root }};
}
Chef template (ERB):
<%# cookbooks/nginx_cookbook/templates/default.conf.erb %>
server {
listen <%= node['nginx']['listen_port'] %>;
server_name <%= node['nginx']['server_name'] %>;
root <%= node['nginx']['root'] %>;
}
The ERB syntax uses <%= ... %> for outputting attribute values. You can also use Ruby logic with <% ... %> (no =) for conditionals.
6. Adapt Roles and Role-Based Patterns
If you use Ansible roles extensively, Chef provides two mechanisms: Chef roles (classic) and Policyfiles (recommended modern approach). For a direct migration, start with Chef roles. An Ansible role is essentially a structured directory; in Chef, a role is a JSON or Ruby DSL object that specifies a run-list and attribute overrides.
Example: An Ansible role webserver that applies the Nginx playbook and maybe a firewall configuration. In Chef, you create a role file (e.g., roles/webserver.json):
{
"name": "webserver",
"description": "Web server role",
"run_list": [
"recipe[nginx_cookbook::default]",
"recipe[firewall::default]"
],
"default_attributes": {
"nginx": {
"listen_port": 8080
}
}
}
Upload the role to the Chef server with knife upload roles/ or knife role from file webserver.json. Then assign the role to a node during bootstrap or via knife.
For a more modern, version-locked approach, consider converting roles to Policyfiles. A Policyfile defines a named policy with specific cookbook versions and a run-list, providing immutable, testable infrastructure definitions.
7. Bootstrap Nodes and Replace Ansible Inventory
Ansible uses inventory files (static or dynamic) to determine which hosts to manage. Chef maintains a server-side registry of nodes. To bring a node under Chef management, you bootstrap it β this installs Chef Infra Client, generates keys, and registers the node with the Chef server.
Bootstrapping a Linux node:
knife bootstrap 192.168.1.10 \
--ssh-user ubuntu \
--ssh-identity-file ~/.ssh/mykey.pem \
--node-name web-node-01 \
--run-list "role[webserver]"
For Windows nodes, use WinRM-based bootstrapping. After bootstrap, the node will run Chef client on a schedule (default every 30 minutes) or you can trigger a run via knife ssh or chef-client command remotely.
To replace dynamic inventory, you can use knife search to query node attributes (e.g., knife search node "role:webserver") or integrate Chef with cloud providers via knife plugins (e.g., knife-ec2, knife-azure).
8. Implement Chef Cookbook CI/CD and Testing
One of Chefβs strengths is its testing ecosystem. Migrate your Ansible testing (e.g., molecule) to Chefβs Test Kitchen and InSpec. Set up a workflow that lints, unit tests, and integration tests your cookbooks.
Create a .kitchen.yml in the cookbook root:
---
driver:
name: vagrant
provisioner:
name: chef_zero
verifier:
name: inspec
platforms:
- name: ubuntu-20.04
- name: centos-8
suites:
- name: default
run_list:
- recipe[nginx_cookbook::default]
verifier:
inspec_tests:
- test/integration/default
Then write an InSpec test (test/integration/default/nginx_spec.rb):
describe package('nginx') do
it { should be_installed }
end
describe service('nginx') do
it { should be_enabled }
it { should be_running }
end
describe port(80) do
it { should be_listening }
end
Run kitchen test to converge a VM and validate the state. Integrate this into CI/CD pipelines (Jenkins, GitHub Actions) for continuous validation.
9. Gradual Rollout and Cutover
Migrating everything at once is risky. Use a phased approach:
- Parallel run phase β Run Chef in parallel with Ansible on a subset of nodes. Use Chefβs
why-runmode or simply apply recipes while Ansible is still active, comparing outcomes. - Canary nodes β Migrate low-risk, non-critical servers first. Verify using InSpec compliance scans.
- Role-based migration β Convert one role (e.g., base monitoring) entirely to Chef, then move on to the next.
- Decommission Ansible β Once a service is fully managed by Chef and confidence is high, stop Ansible runs for that group. Update documentation and alerting.
- Final cutover β After all nodes are managed by Chef, retire Ansible control infrastructure, but keep playbooks archived for reference.
Best Practices for a Smooth Migration
- Start with simple, stateless configurations β Package installs, file deployments, service management. Leave complex orchestration (multi-step deployments) for later.
- Use community cookbooks as a starting point β Search the Chef Supermarket for cookbooks that match your Ansible roles, then customize them rather than writing from scratch.
- Version pin your cookbooks β Use Policyfiles or
Berkshelfwith aBerksfileand lockfile to ensure repeatable deployments, akin to Ansible'srequirements.ymlwith versions. - Leverage Chefβs encrypted data bags for secrets management, replacing Ansible Vault. Alternatively, integrate with HashiCorp Vault using the
chef-vaultcookbook. - Convert idempotent shell/command tasks carefully β In Ansible, you might use
commandorshellmodules withcreatesguards. In Chef, useexecuteresources withnot_iforonly_ifguards to preserve idempotency. - Adopt a testing pyramid β Use Cookstyle (lint), ChefSpec (unit), and Test Kitchen (integration) to catch regressions early.
- Train your team on Ruby basics β Since Chef recipes are Ruby, understanding Rubyβs data types, conditionals, and blocks will empower developers to write more efficient and readable code.
- Document the mapping β Maintain a living document that maps Ansible constructs to Chef equivalents; this aids onboarding and ongoing maintenance.
Conclusion
Migrating from Ansible to Chef is a significant but manageable undertaking that shifts your infrastructure automation from a procedural push model to a desired-state convergence model. By systematically assessing your current Ansible estate, mapping concepts, converting playbooks into Chef recipes and cookbooks, and leveraging Chef's robust testing and policy management features, you can achieve a successful migration without sacrificing reliability. Embrace incremental rollout, invest in Chef's testing ecosystem, and follow best practices around cookbook versioning and secrets management. The result will be a scalable, continuously compliant infrastructure that aligns with enterprise-grade configuration management requirements.