Configuration Management After Containers: What Ansible Still Does Best
I spent two years calling Ansible legacy, then inherited forty Ubuntu boxes with no SSH hardening and found it was still the fastest honest answer.
I spent two years telling anyone who asked that Ansible was legacy, a thing you reach for when Kubernetes has not arrived yet. Then I inherited forty Ubuntu boxes with no SSH hardening, no consistent patch schedule, and three different versions of the same NTP configuration.
Rebuilding them as containers was not an option. They ran vendor agents, licensed software, and a monitoring daemon that needed the host. I wrote a playbook. Nine minutes later, everything was consistent.
The boundary that actually matters
The useful question is not whether configuration management is old. It is what layer the thing you are changing lives at.
If the answer is "the process and its libraries," that belongs in the image. An image rebuild is the right mechanism: immutable, versioned, rolled back by changing a tag.
If the answer is "the kernel, the init system, the firewall, the mounted filesystem, the join to a directory service," that belongs on the host. An image cannot set a sysctl on the node it is running on top of.
Everything confusing about this topic comes from trying to push host concerns into the image, or image concerns into a playbook.
What belongs in the image
Anything the application needs in order to run, decided at build time.
FROM python:3.12-slim
RUN apt-get update && apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
Operating system packages, language runtimes, dependency versions, the directory layout, the non-root user. All of it is reproducible from a Dockerfile, all of it is reviewable in a pull request, and none of it should be reinstalled by a playbook at 2am.
This is the argument made at length in multi-stage builds: separate what you need to build from what you need to run, and ship only the second half.
What belongs in the playbook
The host layer. There is no image to rebuild there.
- Kernel parameters: connection tracking, file limits, transparent huge pages.
- Firewall rules and the default policy for a node that is not running a pod.
- Users, sudoers, SSH configuration, and which keys are authorised.
- Agents: log shippers, monitoring exporters, backup clients.
- Scheduled work that has nothing to do with your deploy: log rotation for the OS, package updates, certificate renewal.
# hosts/web.yml
- name: baseline the host
hosts: web
become: true
tasks:
- name: disable password auth
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: "^PasswordAuthentication"
line: "PasswordAuthentication no"
validate: "sshd -t -f %s"
notify: restart sshd
That last line is the part people forget. validate runs the service's own config check before restarting, so a syntax mistake fails the task instead of disconnecting forty machines at once.
Where I still reach for it weekly
First-run provisioning. A new node needs users, sysctls, and agents before Kubernetes will accept it. Ten minutes of playbook beats an afternoon of clicking.
Compliance evidence. "Is password authentication disabled on every host" is a one-line check against a host group, and the answer is auditable rather than assumed.
Out-of-band repair. When a cluster control plane is unavailable, the thing that fixes it is SSH and a script. A playbook you have run a hundred times is that script with idempotency.
Anything with a runbook. The steps in Linux server hardening are exactly this shape: ordered, repeatable, and safe to run again.
What I no longer do with it
I do not use it to deploy application code. The image does that. The platform restarts the pods. Running a playbook that copies a build onto a machine puts a mutable host back into a system that was otherwise reproducible.
I also do not use it to render configuration the scheduler owns. ConfigMaps and mounted files keep pod-level settings next to the workload, where a restart picks them up without touching the node.
ansible-playbook -i inventory/hosts site.yml --check --diff
--check --diff before every apply is the habit that makes this safe. It is a dry run that prints what would change, which is the same instinct as reviewing a plan before applying it.
Summary
Ask which layer the change belongs to. If it is the process and its libraries, rebuild the image. If it is the kernel, the firewall, the users, or the agents, write the playbook. Ansible is not competing with your container platform; it is covering the surface the container platform deliberately does not reach, and it is still the fastest way to make forty hosts tell the same truth.
SDP Clouds Team
DevOps and cloud engineers writing practical, battle-tested guides on CI/CD, Kubernetes, infrastructure as code, and production operations: every article is based on real incidents and real pipelines, not docs-page rewrites.
More about us →