Building Docker images using Dockerfile
s 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.
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:
&&
to reduce layers.Worse, each RUN
, COPY
, or ADD
adds a new layer—even if you squash layers later, complexity builds up fast.
With Packer, you define the image lifecycle in JSON or HCL. Combine that with Ansible, and you get a declarative, readable, and modular approach.
.
├── ansible/
│ ├── install.yml
│ └── roles/
│ ├── python
│ ├── node
│ └── webapp
├── packer/
│ └── docker-image.json
└── README.md
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
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.