Skip to content

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:

molecule.yml
---
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.

requirements.yml
---
collections:
  - name: containers.podman
    version: ">=1.10.0"

Inventory Structure

Container definitions are managed through standard Ansible inventory:

inventory/hosts.yml
---
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.

inventory/group_vars/molecule.yml
---
# 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

create.yml
---
- 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.

tasks/create-fail.yml
---
- 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

converge.yml
---
- 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

verify.yml
---
- 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

cleanup.yml
---
- 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

destroy.yml
---
- 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.