Using podman containers¶
Note
This example demonstrates the use of an ansible-native configuration.
This example demonstrates testing with Podman containers using standard Ansible inventory and playbooks for complete control over the testing environment.
Overview¶
This scenario uses:
- Inventory management: Standard Ansible inventory files for container definitions
- Container lifecycle: Managed through Ansible playbooks with full customization
- Flexible configuration: Container settings defined in inventory for easy modification
When you run molecule test --scenario-name podman, Molecule executes the complete test sequence including create, converge, verify, cleanup, and destroy steps using your custom playbooks.
Configuration¶
The configuration uses standard Ansible patterns:
---
ansible:
cfg:
defaults:
deprecation_warnings: false
executor:
args:
ansible_playbook:
- --inventory=inventory/
dependency:
name: galaxy
options:
requirements-file: ${MOLECULE_SCENARIO_DIRECTORY}/requirements.yml
scenario:
test_sequence:
- dependency
- destroy
- create
- converge
- idempotence
- verify
- cleanup
- destroy
Key points: The ansible section configures inventory location and executor arguments. The default driver indicates pure Ansible automation without Molecule-specific drivers.
Inventory Structure¶
Container definitions are managed through standard Ansible inventory:
---
all:
children:
molecule:
hosts:
molecule-fedora:
ansible_connection: containers.podman.podman
container_image: ghcr.io/ansible/community-ansible-dev-tools:latest
container_command: sleep 1d
container_privileged: false
Key points: Containers are defined as inventory hosts with connection and configuration details. The molecule group contains test targets.
When selecting the container image, remember that the container image must have Python installed to be able to run many of the builtin tasks.
---
# Molecule group variables for container configuration
container_log_driver: json-file
container_systemd: false
Key points: Group variables provide shared configuration for container settings.
Lifecycle Playbooks¶
Create Playbook¶
---
- name: Create container instances
hosts: localhost
gather_facts: false
tasks:
- name: Create containers from inventory
containers.podman.podman_container:
hostname: "{{ item }}"
name: "{{ item }}"
image: "{{ hostvars[item]['container_image'] }}"
command: "{{ hostvars[item]['container_command'] | default('sleep 1d') }}"
privileged: "{{ hostvars[item]['container_privileged'] | default(false) }}"
volumes: "{{ hostvars[item]['container_volumes'] | default(omit) }}"
capabilities: "{{ hostvars[item]['container_capabilities'] | default(omit) }}"
systemd: "{{ hostvars[item]['container_systemd'] | default(false) }}"
log_driver: "{{ hostvars[item]['container_log_driver'] | default('json-file') }}"
state: started
register: result
loop: "{{ groups['molecule'] }}"
- name: Verify containers are running
ansible.builtin.include_tasks:
file: tasks/create-fail.yml
when: >
item.container.State.ExitCode != 0 or
not item.container.State.Running
loop: "{{ result.results }}"
loop_control:
label: "{{ item.container.Name }}"
- name: Wait for containers to be ready
ansible.builtin.wait_for_connection:
timeout: 30
delegate_to: "{{ item }}"
loop: "{{ groups['molecule'] }}"
Key points: Creates containers based on inventory definitions using hostvars for configuration. Includes error handling and connection verification.
---
- name: Retrieve container log
ansible.builtin.command:
cmd: podman logs {{ item.container.Name }}
changed_when: false
register: logfile_cmd
- name: Display container log and fail
ansible.builtin.fail:
msg: |
Container {{ item.container.Name }} failed to start properly.
Exit Code: {{ item.container.State.ExitCode }}
Running: {{ item.container.State.Running }}
Log output: {{ logfile_cmd.stdout | default('No logs available') }}
Key points: Error handling task that displays container logs when creation fails.
Converge Playbook¶
---
- name: Converge
hosts: molecule
gather_facts: true
tasks:
- name: Read OS release information
ansible.builtin.slurp:
src: /etc/os-release
register: os_release
- name: Write OS info to file
ansible.builtin.copy:
content: "{{ os_release.content | b64decode }}"
dest: /tmp/molecule_os_info.txt
mode: "0644"
Key points: Validates inventory structure, gathers facts from containers, reads OS information, and writes data to a temporary file for verification. Uses proper Ansible modules instead of raw commands thanks to the Python-enabled container image.
Verify Playbook¶
---
- name: Verify
hosts: molecule
gather_facts: true
tasks:
- name: Read OS info file created during converge
ansible.builtin.slurp:
src: /tmp/molecule_os_info.txt
register: os_info_file
- name: Verify OS is Fedora-based
ansible.builtin.assert:
that:
- os_correct == true
- os_family_correct == true
fail_msg: "Expected Fedora Linux-based OS, but got: {{ os_info }} and {{ ansible_facts['os_family'] }}"
success_msg: "Successfully verified Fedora Linux-based container"
vars:
os: Fedora Linux
os_family: RedHat
os_info: "{{ os_info_file.content | b64decode }}"
os_correct: "{{ os in os_info }}"
os_family_correct: "{{ os_family == ansible_facts['os_family'] }}"
- name: Verify the dev tools environment variable
ansible.builtin.assert:
that:
- env_var_value == expected
fail_msg: "Expected {{ env_var }} to be {{ expected }}, but got: {{ env_var_value }}"
success_msg: "Successfully verified {{ env_var }}"
vars:
env_var: ANSIBLE_DEV_TOOLS_CONTAINER
env_var_value: "{{ ansible_env['ANSIBLE_DEV_TOOLS_CONTAINER'] }}"
expected: "1"
Key points: Reads the temporary file created during converge, verifies the container OS is Fedora-based, and validates the dev tools environment variable. Demonstrates comprehensive container testing and validation.
Cleanup Playbook¶
---
- name: Cleanup container instances
hosts: molecule
gather_facts: false
tasks:
- name: Check if container is running
containers.podman.podman_container_info:
name: "{{ inventory_hostname }}"
register: container_info
delegate_to: localhost
- name: Remove temporary files from running containers
ansible.builtin.file:
path: /tmp/molecule_os_info.txt
state: absent
when:
- container_info.containers | length > 0
- container_info.containers[0].State.Running
failed_when: false
Key points: Intelligently removes temporary files created during testing, but only when containers exist and are running. Includes robust error handling for edge cases.
Destroy Playbook¶
---
- name: Destroy container instances
hosts: localhost
gather_facts: false
tasks:
- name: Get info for all containers
containers.podman.podman_container_info:
name: "{{ item }}"
loop: "{{ groups['molecule'] }}"
register: podman_infos
- name: Kill container if running
containers.podman.podman_container:
name: "{{ item.item }}"
state: stopped
timeout: 2
loop: "{{ podman_infos.results }}"
loop_control:
label: "{{ item.item }}"
when:
- item.containers | length > 0
- item.containers[0].State.Status == "running"
Key points: Cleanly removes containers using inventory host definitions with error handling.
Benefits¶
- Standard practices: Uses familiar Ansible inventory and playbook patterns
- Full control: Complete customization of container lifecycle and configuration
- Modern tooling: Leverages Python-enabled container images for full Ansible module support
- Comprehensive testing: Includes converge, verify, and cleanup phases for thorough validation
- Reusable: Inventory can be shared across multiple scenarios
- Maintainable: Clear separation of concerns between configuration and automation
- Flexible: Easy to extend with additional container settings, networking, or testing logic
- Robust: Intelligent error handling and conditional execution for reliable testing
This approach provides the foundation for more complex testing scenarios while maintaining simplicity and demonstrating modern ansible-native practices with comprehensive lifecycle management.