← Back to DevBytes

Migrating from Ansible to Chef: Step-by-Step Guide

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:

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:

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:

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:

Best Practices for a Smooth Migration

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.

πŸš€ Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started β€” $23.99/mo
← Back to all articles