<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Podman &#8211; Made For Cloud</title>
	<atom:link href="https://madeforcloud.com/category/podman/feed/" rel="self" type="application/rss+xml" />
	<link>https://madeforcloud.com</link>
	<description>Just another WordPress site</description>
	<lastBuildDate>Fri, 17 Jul 2026 04:54:18 +0000</lastBuildDate>
	<language>en-AU</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0.2</generator>
	<item>
		<title>Molecule + Podman: Why It’s a Pain, but here&#8217;s an Example That Finally Works</title>
		<link>https://madeforcloud.com/2026/07/17/molecule-podman-why-its-a-pain-but-heres-an-example-that-finally-works/</link>
					<comments>https://madeforcloud.com/2026/07/17/molecule-podman-why-its-a-pain-but-heres-an-example-that-finally-works/#respond</comments>
		
		<dc:creator><![CDATA[gocallag]]></dc:creator>
		<pubDate>Fri, 17 Jul 2026 04:47:43 +0000</pubDate>
				<category><![CDATA[Ansible]]></category>
		<category><![CDATA[Podman]]></category>
		<guid isPermaLink="false">https://madeforcloud.com/?p=318</guid>

					<description><![CDATA[If you’ve ever tried to run Molecule with Podman instead of Docker, you already know the truth: it never “just works.” Between dynamic inventories, missing Python interpreters, inconsistent container images, and Molecule’s habit of generating YAML that looks like it was written by a committee, you spend more time debugging the framework than testing your&#8230;<p><a class="more-link" href="https://madeforcloud.com/2026/07/17/molecule-podman-why-its-a-pain-but-heres-an-example-that-finally-works/" title="Continue reading &#8216;Molecule + Podman: Why It’s a Pain, but here&#8217;s an Example That Finally Works&#8217;">Continue reading <span class="meta-nav">&#8594;</span></a></p>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">If you’ve ever tried to run Molecule with Podman instead of Docker, you already know the truth: <strong>it never “just works.”</strong> Between dynamic inventories, missing Python interpreters, inconsistent container images, and Molecule’s habit of generating YAML that looks like it was written by a committee, you spend more time debugging the framework than testing your Ansible roles.</p>



<p class="wp-block-paragraph">This post walks through a <strong>real, working Molecule + Podman setup</strong> that supports <strong>multiple OS containers</strong> (Ubuntu, Rocky, CentOS Stream), uses <strong>Podman natively</strong>, and avoids the usual landmines:</p>



<ul class="wp-block-list">
<li>Python missing inside containers</li>



<li>Fact‑gathering failures</li>



<li>Molecule’s dynamic inventory breaking</li>



<li>Destroy steps leaving orphaned containers</li>



<li>Podman connection plugin quirks</li>
</ul>



<p class="wp-block-paragraph">If you’ve been fighting Molecule + Podman, this example will save you hours.</p>



<h2 class="wp-block-heading"><strong>The Problem: Molecule + Podman Is Not a Drop‑In Replacement for Docker</strong></h2>



<p class="wp-block-paragraph">Molecule’s Docker driver is mature. Podman’s driver… well&#8230; it isn’t.</p>



<p class="wp-block-paragraph">If you’re testing roles across multiple OSes, these problems multiply and you eventually just give up.</p>



<h2 class="wp-block-heading"><strong>The Working Example: Multi‑OS Molecule Scenario Using Podman</strong></h2>



<p class="wp-block-paragraph">Here’s the structure:</p>



<pre class="wp-block-code"><code>molecule/default/
├── create.yml
├── prepare.yml
├── converge.yml
├── verify.yml
├── destroy.yml
├── molecule.yml
└── requirements.yml
</code></pre>



<h3 class="wp-block-heading"><strong>Key idea:</strong></h3>



<p class="wp-block-paragraph">We <strong>start our own containers with podman </strong>and generate our <strong>own dynamic inventory</strong> inside <code>create.yml</code> and force Molecule to use it.</p>



<p class="wp-block-paragraph">This avoids Molecule’s broken Podman inventory plugin entirely.</p>



<h2 class="wp-block-heading"><strong>1. create.yml  Start Containers + Build Inventory</strong></h2>



<p class="wp-block-paragraph">We define our Podman containers, start them and force feed the inventory to molecule:</p>



<pre class="wp-block-code"><code>- name: Create
  hosts: localhost
  gather_facts: false
  vars:
    molecule_inventory:
      all:
        hosts: {}
        molecule: {}
    
    podman:
      - name: ubuntu-2404
        image: ubuntu:24.04
        command: /bin/bash
        tty: true
      - name: rocky9
        image: quay.io/rockylinux/rockylinux:9  
        command: /bin/bash
        tty: true
      - name: rocky10
        image: quay.io/rockylinux/rockylinux:10
        command: /bin/bash
        tty: true
      - name: cs9
        image: quay.io/centos/centos:stream9
        command: /bin/bash
        tty: true
  tasks:

    - name: Start Podman container(s)
      containers.podman.podman_container:
        name: "{{ item.name }}"
        image: "{{ item.image }}"
        state: started
        command: "{{ item.command | default(omit) }}"
        tty: "{{ item.tty | default(omit) }}"
      register: result
      loop: "{{ podman }}"
      loop_control:
        label: "{{ item.name }}"

    - name: Add container to molecule_inventory
      vars:
        inventory_partial_yaml: |
          all:
            children:
              molecule:
                hosts:
                  "{{ item.name }}":
                    ansible_connection: containers.podman.podman
      ansible.builtin.set_fact:
        molecule_inventory: &gt;
          {{ molecule_inventory | combine(inventory_partial_yaml | from_yaml, recursive=true) }}
      loop: "{{ podman }}"
      loop_control:
        label: "{{ item.name }}"

    - name: Dump molecule_inventory
      ansible.builtin.copy:
        content: |
          {{ molecule_inventory | to_yaml }}
        dest: "{{ molecule_ephemeral_directory }}/inventory/molecule_inventory.yml"
        mode: "0600"

    - name: Force inventory refresh
      ansible.builtin.meta: refresh_inventory

    - name: Fail if molecule group is missing
      ansible.builtin.assert:
        that: "'molecule' in groups"
        fail_msg: |
          molecule group was not found inside inventory groups: {{ groups }}
      run_once: true # noqa: run-once&#91;task]

# we want to avoid errors like "Failed to create temporary directory"
- name: Validate that inventory was refreshed
  hosts: molecule
  gather_facts: false
  tasks:
    - name: Check uname
      ansible.builtin.raw: uname -a
      register: result
      changed_when: false

    - name: Display uname info
      ansible.builtin.debug:
        msg: "{{ result.stdout }}"
</code></pre>



<p class="wp-block-paragraph">It&#8217;s important to note, we added our containers to the inventory with the podman ansible_connection,  <code>containers.podman.podman_container</code>.</p>



<p class="wp-block-paragraph">Out<strong> inventory file </strong>under the covers looks like this:</p>



<pre class="wp-block-code"><code>all:
  children:
    molecule:
      hosts:
        ubuntu-2404:
          ansible_connection: containers.podman.podman
        rocky9:
          ansible_connection: containers.podman.podman
        rocky10:
          ansible_connection: containers.podman.podman
        cs9:
          ansible_connection: containers.podman.podman
</code></pre>



<p class="wp-block-paragraph">And note that we <strong>force Molecule to refresh</strong>:</p>



<pre class="wp-block-code"><code>- meta: refresh_inventory
</code></pre>



<p class="wp-block-paragraph">This is the magic step most examples miss.</p>



<h2 class="wp-block-heading"><strong>2. prepare.yml  Only Touch Ubuntu Containers</strong></h2>



<p class="wp-block-paragraph">Instead of conditionals, we use a <strong>host pattern</strong> and fix the missing python3 in the Ubuntu containers.</p>



<pre class="wp-block-code"><code>- hosts: "ubuntu*"
  gather_facts: false
  tasks:
    - name: Refresh apt cache Ubuntu container
      raw: |
        apt-get update
    - name: Install Python inside Ubuntu container
      raw: |
        apt-get install -y python3
    - name: Set correct Python interpreter for Ubuntu
      set_fact:
        ansible_python_interpreter: /usr/bin/python3

</code></pre>



<p class="wp-block-paragraph">Why this works:</p>



<ul class="wp-block-list">
<li>Molecule names your Ubuntu containers <code>ubuntu-*</code></li>



<li>No need for OS detection</li>



<li>No need for fact gathering</li>
</ul>



<p class="wp-block-paragraph">This is the cleanest possible prepare step.</p>



<h2 class="wp-block-heading"><strong>3. converge.yml  Run Tasks on All Containers</strong></h2>



<p class="wp-block-paragraph">This is the thing we&#8217;re testing</p>



<pre class="wp-block-code"><code>- hosts: all
  tasks:
    - shell: "cat /etc/*release*"
      register: result
    - debug: msg="{{ result }}"
</code></pre>



<p class="wp-block-paragraph">Simple, predictable, works across all OSes or well, it does what you want your code to do.</p>



<h2 class="wp-block-heading"><strong>4. verify.yml  OS‑Specific Assertions</strong></h2>



<pre class="wp-block-code"><code>- hosts: rocky9
  tasks:
    - assert:
        that:
          - "'dnf' in ansible_facts.packages"

- hosts: ubuntu-2404
  tasks:
    - assert:
        that:
          - "'apt' in ansible_facts.packages"
</code></pre>



<p class="wp-block-paragraph">This is how you validate OS‑specific behaviour cleanly, again, you do you.</p>



<h2 class="wp-block-heading"><strong>5. destroy.yml  Clean Container Removal</strong></h2>



<pre class="wp-block-code"><code>- name: Destroy molecule containers
  hosts: molecule
  gather_facts: false
  tasks:
    - name: Delete Podman container(s)
      containers.podman.podman_container:
        name: "{{ item }}"
        state: absent
      loop: "{{ groups&#91;'all'] | difference(&#91;'localhost']) }}"
      delegate_to: localhost

- name: Remove dynamic molecule inventory
  hosts: localhost
  gather_facts: false
  tasks:

    - name: Remove dynamic inventory file
      ansible.builtin.file:
        path: "{{ molecule_ephemeral_directory }}/inventory/molecule_inventory.yml"
        state: absent
</code></pre>



<p class="wp-block-paragraph">This avoids:</p>



<ul class="wp-block-list">
<li>Molecule leaving orphaned containers</li>



<li>Podman complaining about missing names</li>



<li>localhost being treated as a container</li>
</ul>



<h2 class="wp-block-heading"><strong>Why This Example Works When Others Don’t</strong></h2>



<h3 class="wp-block-heading"><strong>1. We bypass Molecule’s broken Podman inventory plugin</strong></h3>



<p class="wp-block-paragraph">We generate our own containers and inventory and force Molecule to use it.</p>



<h3 class="wp-block-heading"><strong>2. We use host patterns instead of OS detection</strong></h3>



<p class="wp-block-paragraph">Fact gathering doesn’t work until Python is installed.</p>



<h3 class="wp-block-heading"><strong>3. We use Podman’s connection plugin correctly</strong></h3>



<p class="wp-block-paragraph">Every host gets:</p>



<pre class="wp-block-code"><code>ansible_connection: containers.podman.podman
</code></pre>



<h3 class="wp-block-heading"><strong>4. We refresh inventory explicitly</strong></h3>



<p class="wp-block-paragraph">Without this, Molecule uses stale inventory.</p>



<h2 class="wp-block-heading"><strong>Conclusion: Molecule + Podman Is Painful &#8230;. But Fixable</strong></h2>



<p class="wp-block-paragraph">Podman is great. Molecule is great. Together? They fight.</p>



<p class="wp-block-paragraph">But with:</p>



<ul class="wp-block-list">
<li>explicit container creation</li>



<li>explicit inventory generation</li>



<li>host patterns</li>



<li>correct Podman connection plugin</li>



<li>correct Ubuntu prepare steps</li>



<li>correct destroy logic</li>
</ul>



<p class="wp-block-paragraph">You can run a <strong>multi‑OS Molecule test matrix</strong> under Podman reliably.</p>



<p class="wp-block-paragraph">This example proves it.</p>



<p class="wp-block-paragraph">If you’re building cloud automation, CI pipelines, or multi‑distro Ansible roles, this pattern will save you hours of frustration.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://madeforcloud.com/2026/07/17/molecule-podman-why-its-a-pain-but-heres-an-example-that-finally-works/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>phpIPAM via podman-compose</title>
		<link>https://madeforcloud.com/2022/07/09/phpipam-via-podman-compose/</link>
					<comments>https://madeforcloud.com/2022/07/09/phpipam-via-podman-compose/#respond</comments>
		
		<dc:creator><![CDATA[gocallag]]></dc:creator>
		<pubDate>Sat, 09 Jul 2022 06:00:46 +0000</pubDate>
				<category><![CDATA[Podman]]></category>
		<guid isPermaLink="false">http://168.138.6.194/?p=136</guid>

					<description><![CDATA[You can use containers inside container orchestration platforms and of course you can do that with phpIPAM as well, but in my case I just wanted the convenience of the container packaging approach and running it on a single Linux host without having to worry about the overheads of K8S style platforms. I was using&#8230;<p><a class="more-link" href="https://madeforcloud.com/2022/07/09/phpipam-via-podman-compose/" title="Continue reading &#8216;phpIPAM via podman-compose&#8217;">Continue reading <span class="meta-nav">&#8594;</span></a></p>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">You can use containers inside container orchestration platforms and of course you can do that with phpIPAM as well, but in my case I just wanted the convenience of the container packaging approach and running it on a single Linux host without having to worry about the overheads of K8S style platforms.</p>



<p class="wp-block-paragraph">I was using a RHEL derivative, Alma Linux 9.0 in this case and also using Podman rather than Docker.</p>



<p class="wp-block-paragraph">I did want to use the docker-compose approach to configuring and maintaining the application. The compose format makes it really quite simple to deploy and maintain simple container applications that are single system hosted.</p>



<p class="wp-block-paragraph">Since I wasn&#8217;t using Docker, rather Podman, I found that you can use a tool called podman-compose to orchestrate podman to deliver the outcome you&#8217;d expect from a docker-compose file.</p>



<p class="wp-block-paragraph">Firstly, start like this, getting podman and pip3 installed.</p>



<pre class="wp-block-code"><code> yum install podman python3-pip</code></pre>



<p class="wp-block-paragraph">Then it&#8217;s simple to install podman-compose</p>



<pre class="wp-block-code"><code>pip3 install podman-compose</code></pre>



<p class="wp-block-paragraph">With a docker-compose.yml file similar to the following (change the default passwords i&#8217;ve put in the file) you can get going very quickly.</p>



<pre class="wp-block-code"><code>version: '3'

services:
  phpipam-web:
    image: docker.io/phpipam/phpipam-www:latest
    ports:
      - "80:80"
    environment:
      - TZ=Australia/Melbourne
      - IPAM_DATABASE_HOST=phpipam-mariadb
      - IPAM_DATABASE_USER=root
      - IPAM_DATABASE_PASS=&lt;mysql_root_pass&gt;
    restart: unless-stopped
    volumes:
      - phpipam-logo:/phpipam/css/images/logo
    depends_on:
      - phpipam-mariadb

  phpipam-mariadb:
    image: docker.io/library/mariadb:latest
    environment:
      - MARIADB_ROOT_PASSWORD=&lt;mysql_root_pass&gt;
    restart: unless-stopped
    volumes:
      - phpipam-db-data:/var/lib/mysql

volumes:
  phpipam-db-data:
  phpipam-logo:</code></pre>



<p class="wp-block-paragraph">Then it&#8217;s as simple as</p>



<pre class="wp-block-code"><code>podman-compose up -d</code></pre>



<p class="wp-block-paragraph">Then you connect to the IP address of your underlying system, and execute the installation dialogue.  You should only need to enter the MySQL/MariaDB username / password, everything else should be pre-filled with the correct information.</p>



<figure class="wp-block-image size-full"><img fetchpriority="high" decoding="async" width="629" height="567" src="http://168.138.6.194/wp-content/uploads/2022/07/image-2-1.png" alt="" class="wp-image-141" srcset="https://madeforcloud.com/wp-content/uploads/2022/07/image-2-1.png 629w, https://madeforcloud.com/wp-content/uploads/2022/07/image-2-1-300x270.png 300w" sizes="(max-width: 629px) 100vw, 629px" /></figure>
]]></content:encoded>
					
					<wfw:commentRss>https://madeforcloud.com/2022/07/09/phpipam-via-podman-compose/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
