Container Image Creation: Ansible + Packer vs Dockerfiles

Building Docker images using Dockerfiles is common practice, but as your application stack grows in complexity, so do your Dockerfiles. Long chains of RUN commands strung together with &&, fragile shell logic, and layer sprawl can quickly make images harder to manage and debug. In this blog post, we’ll show how to use Ansible with HashiCorp Packer to build cleaner, single-layered, and more maintainable Docker images—without the overhead of managing long Dockerfiles.


Why Not Just Use a Dockerfile?

Here’s a simplified snippet of a typical "complex" Dockerfile:

FROM ubuntu:22.04

RUN apt-get update && \
    apt-get install -y python3 python3-pip build-essential curl git nginx && \
    pip3 install flask gunicorn requests && \
    curl -fsSL https://deb.nodesource.com/setup_18.x | bash - && \
    apt-get install -y nodejs && \
    git clone https://github.com/your-org/yourapp.git /opt/app && \
    cd /opt/app && npm install && \
    rm -rf /var/lib/apt/lists/*


This image:

  • Chains multiple package installations and tools with && to reduce layers.
  • Installs system packages, Python libraries, Node.js, and your app in a single block.
  • Is brittle to changes: a failure mid-chain often requires rerunning everything.
  • Is hard to read, modify, and debug.

Worse, each RUN, COPY, or ADD adds a new layer—even if you squash layers later, complexity builds up fast.

Enter Packer + Ansible: Cleaner, One-Layer Docker Images

With Packer, you define the image lifecycle in JSON or HCL. Combine that with Ansible, and you get a declarative, readable, and modular approach.

Benefits:

  • One image layer (Packer builds a single output image).
  • Separation of concerns (Ansible manages configuration; Packer handles image creation).
  • Easier testing and reuse of roles.
  • Better readability and idempotent installs.


Step-by-Step: Building a Docker Image with Ansible + Packer

1. Project Structure

.
├── ansible/
│   ├── install.yml
│   └── roles/
│       ├── python
│       ├── node
│       └── webapp
├── packer/
│   └── docker-image.json
└── README.md


2. Ansible Playbook (ansible/install.yml)

- hosts: all
  become: true
  roles:
    - python
    - node
    - webapp


Each role is modular and easy to maintain. For example:


roles/python/tasks/main.yml

- name: Install Python and pip
  apt:
    name:
      - python3
      - python3-pip
    update_cache: yes
    state: present

- name: Install Python packages
  pip:
    name:
      - flask
      - gunicorn
      - requests


roles/node/tasks/main.yml

- name: Install Node.js (via Nodesource)
  shell: curl -fsSL https://deb.nodesource.com/setup_18.x | bash -
  args:
    warn: false

- name: Install Node.js
  apt:
    name: nodejs
    state: present


roles/webapp/tasks/main.yml

- name: Clone application repo
  git:
    repo: 'https://github.com/your-org/yourapp.git'
    dest: /opt/app

- name: Install app dependencies
  npm:
    path: /opt/app
    production: yes


3. Packer Template (packer/docker-image.json)

{
  "builders": [
    {
      "type": "docker",
      "image": "ubuntu:22.04",
      "commit": true
    }
  ],
  "provisioners": [
    {
      "type": "ansible",
      "playbook_file": "../ansible/install.yml"
    }
  ],
  "post-processors": [
    [
      {
        "type": "docker-tag",
        "repository": "yourcompany/yourapp",
        "tag": "latest"
      },
      {
        "type": "docker-push",
        "login": false
      }
    ]
  ]
}

This results in a single, flattened image layer that is faster to load, simpler to inspect, and smaller in size.


Comparing Results: Dockerfile vs Ansible + Packer

Feature Dockerfile Packer + Ansible
Readability Poor in complex chains High (declarative)
Modularity Low High (reusable)
Layered Output Multi-layered Single layer
Debugging Tedious Easier (role/task granularity)
CI/CD Integration Standard Easily Integrated
Image Size Larger Leaner


Conclusion

Using Ansible with Packer is a powerful alternative to monolithic Dockerfiles, especially when managing complex environments. It promotes cleaner architecture, better separation of concerns, and outputs optimized, single-layer images ready for deployment.

Whether you're building a lightweight microservice or a full-stack application, Packer + Ansible can make your container builds faster, cleaner, and more maintainable.

Want a downloadable example project? Let me know and I’ll prepare a repo-ready starter template.