Skip to main content
RunBook Academy

LinuxLXXV · Immutable vs Mutable InfrastructureConfiguration seam

Configuration management and image baking - choosing the seam

Intermediate⏱ ~16 minansiblepacker

What you'll learn

  • Place a piece of configuration at bake, launch or run time deliberately
  • Reuse configuration management code as an image build step
  • Use a convergence tool as a drift detector rather than a remediator
  • Recognise the failure mode of running both models at once

Prerequisites

Verified against Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL 9.x · Rocky Linux 9.x · AlmaLinux 9.x · Linux kernel 6.1 LTS / 6.6 LTS · systemd 255+ · OpenSSH 8.7p1 (RHEL 9) / 9.6p1 (Ubuntu 24.04) · nftables 1.0.x · chrony 4.x · Pacemaker 2.1.x · Corosync 3.1.x · 2026-08-11

Not yet marked complete on this device.

Immutable infrastructure is often presented as a replacement for configuration management. It is not. It moves configuration management to a different moment - and the interesting work is deciding which moment, for each setting, rather than adopting a tool.

There are exactly three moments available.

MomentApplied bySuits
Bake timeThe image buildAnything identical across every instance of a role
Launch timeuser-data, instance tags, a parameter storeAnything that varies by environment or instance and is known at launch
Run timeA convergence agent on a live hostAnything that must change while the instance is alive

The decision, per setting

Ask three questions in order, and stop at the first yes.

Is it identical for every instance of this role, and does it change rarely? Bake it. Packages, the application binary, systemd units, the base security configuration, the monitoring agent. Baking it makes the boot faster, removes a runtime dependency on a package repository, and means the thing you tested is the thing that runs.

Is it known at launch and different per instance or per environment? Apply it at launch. The environment name, the database endpoint, the log destination, the cluster to join. This is what keeps one image usable in staging and production - promote by tagging, not by rebuilding.

Does it have to change while the instance is alive? Only then does run-time convergence enter. And it is worth pressing on the question, because the honest answer is usually “no, but replacing the fleet takes 40 minutes and I want it now” - which is a pipeline speed problem, not a configuration model problem.

Configuration management as a build step

The playbooks and manifests that used to converge live servers are usually the fastest way to build an image, and reusing them means one definition rather than two. Packer runs them as provisioners:

build {
  sources = ["source.amazon-ebs.web"]

  provisioner "ansible" {
    playbook_file = "./site.yml"
    extra_arguments = ["--extra-vars", "build_mode=true"]
  }

  provisioner "shell" {
    inline = ["sudo ansible --version"]
  }
}

Before you assume the playbook works in that context, check what it actually does:

ansible-playbook --syntax-check site.yml
ansible-playbook --list-tasks site.yml

Then be honest about the differences, because a playbook written for convergence carries assumptions a build machine breaks:

  • Facts. A playbook that branches on the target hostname, its IP address or its environment tag gets the builder’s values. The image then carries decisions made about a machine that existed for eleven minutes.
  • Handlers that restart services. Harmless on a live host, meaningless in a build - and worse, a service started during the build may write state that ends up in the image.
  • Secrets. A playbook that fetches a secret and writes it to disk has just baked it into the image, for every instance, forever. Secrets are a launch-time or run-time concern; see linux-cloud-identity-and-instance-roles.
  • Anything that assumes network identity. Registering with a service discovery system, joining a domain, requesting a certificate. All of these belong at first boot, not in the image, because the image is not a machine.

The usual structure is a build_mode variable that skips the tasks that need a real instance, plus a first-boot role that runs only those. Two entry points, one body of code.

Convergence as a detector, not a remediator

On an immutable fleet, a convergence tool still has a job: it tells you when a machine has diverged from what its image says it should be. Run it in check mode, on a schedule, and treat any diff as a signal.

ansible-playbook --check --diff -i inventory site.yml
Read-only / Safe
$ ansible-playbook --check --diff -i inventory site.yml
TASK [nginx : deploy site config] **********************************
--- before: /etc/nginx/conf.d/site.conf
+++ after: /etc/nginx/conf.d/site.conf
@@ -3,7 +3,7 @@
-    proxy_read_timeout 30s;
+    proxy_read_timeout 5s;
changed: [web-03]

PLAY RECAP *********************************************************
web-03    : ok=24   changed=1    unreachable=0    failed=0

Illustrative output

--check predicts changes without making them and --diff shows them; together they answer “has anything on this host diverged from the definition”. On a mutable fleet you would follow that with a real run. On an immutable fleet a non-zero changed count means something wrote to a machine that should have been replaced instead, and the response is to find out what wrote it, fix the image, and replace the instance.

Puppet’s --noop and equivalents in other tools serve the same purpose. The point is the mode, not the tool.

What to do when you genuinely need a live change

Sometimes a setting has to change now and the pipeline is thirty minutes long. That is a real situation and it deserves a real answer rather than a rule that gets broken quietly:

  • Prefer a mechanism designed for it. A feature flag, a configuration service the application re-reads, a value in a parameter store fetched on a timer. These change behaviour without changing the machine, so the image stays authoritative.
  • If you must change the machine, treat it as a temporary measure with an expiry: record it, mark the instance, and open the change against the image immediately. That is the emergency path, and it is covered in linux-immutable-trade-offs-and-emergency-change.

The distinction that matters: changing an application’s behaviour through data it reads is not mutation of the machine. Editing a file on the machine is.

Knowledge check

Knowledge check · 4 questions

  1. Q1. Where should the database endpoint for an application be applied?

  2. Q2. Which of these must NOT be produced during an image build? Select all that apply.

  3. Q3. Leaving a convergence agent applying changes every thirty minutes on a fleet built from golden images reintroduces the drift the images were meant to eliminate.

  4. Q4. A scheduled ansible-playbook --check --diff run reports one changed task on a single instance in an immutable fleet. What is the correct response?

Passing score: 75%. Answers are checked in this browser.