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.

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.