Anatomy of a Podman Stack

Each application will have a role and an accompanying playbook for deploying the role to a host. The playbook will be arranged assuming that you are deploying in an environment identical to SR2’s (i.e. FreeIPA domain join, NTP servers in our datacenter, Tailscale for monitoring) although the role itself will usually make only basic assumptions about the hosting environment (e.g. podman is installed, subuids and subgids are configured).

Within the role, the tasks file will work through a similar set of tasks for each application:

  • Configure the networks with the sr2c.podman.podman_networks role

  • Configure the host firewall using ansible.posix.firewalld

  • Configure “utility” services (e.g. nginx, PostgreSQL, MariaDB, Redis) using utility roles that include common boilerplate or required hardening

  • Configure the data locations that will be used as volumes with the podman_data role

  • Configure the containers for the application itself with the podman_stack role

  • Any post-startup application-specific configuration (e.g. runtime configuration of security policies)

For a hypothetical application myapp, the tasks file looks like this:

---
- name: Podman Myapp | PATCH | Configure the networks
  ansible.builtin.include_role:
    name: sr2c.podman.podman_networks
  vars:
    podman_networks_podman_rootless_user: "{{ podman_myapp_podman_rootless_user }}"
    podman_networks_config: "{{ _podman_myapp_networks }}"
    podman_networks_stop_units: [myapp.target]

- name: Podman Myapp | PATCH | Open the firewall for HTTP and HTTPS
  ansible.posix.firewalld:
    zone: public
    service: "{{ item }}"
    permanent: true
    immediate: true
    state: enabled
  loop: [http, https]
  become: true

- name: Podman Myapp | PATCH | Configure nginx
  ansible.builtin.include_role:
    name: sr2c.podman.podman_nginx
  vars:
    podman_nginx_podman_rootless_user: "{{ podman_myapp_podman_rootless_user }}"
    podman_nginx_primary_hostname: "{{ podman_myapp_hostname }}"
    podman_nginx_frontend_network: frontend

- name: Podman Myapp | PATCH | Configure the data locations
  ansible.builtin.include_role:
    name: sr2c.podman.podman_data
  vars: {}  # data locations and their permissions

- name: Podman Myapp | PATCH | Configure the application containers
  ansible.builtin.include_role:
    name: sr2c.podman.podman_stack
  vars: {}  # container quadlets and the stack slice and target

- name: Podman Myapp | PATCH | Apply the runtime security policy
  ansible.builtin.command:
    argv: [podman, exec, myapp, myapp-cli, policy, apply]
  become: true
  become_user: "{{ podman_myapp_podman_rootless_user }}"
  register: _podman_myapp_policy
  changed_when: _podman_myapp_policy.rc == 3
  failed_when: _podman_myapp_policy.rc not in [0, 3]