* Rewrite podman and buildah connections --------- Signed-off-by: Sagi Shnaidman <sshnaidm@redhat.com>
29 KiB
Podman and Buildah Connection Plugins
This document provides comprehensive information about the Podman and Buildah connection plugins included in the containers.podman Ansible collection.
Table of Contents
- Overview
- Installation and Setup
- Podman Connection Plugin
- Buildah Connection Plugin
- Configuration Options
- Simple Usage Examples
- Advanced Usage Examples
- Performance and Best Practices
- Troubleshooting
- Migration from Legacy Plugins
Overview
The connection plugins enable Ansible to execute tasks directly inside Podman containers and Buildah working containers. These plugins provide a seamless way to manage containerized environments using standard Ansible playbooks and tasks.
Key Features
- Direct container execution: Run Ansible tasks inside containers without SSH
- Fail-fast error handling: No implicit retries; commands return rc/stdout/stderr to Ansible
- Performance optimization: Connection caching and mount detection for efficient file operations
- Environment management: Inject environment variables and configure container runtime
- Unicode and special character support: Handle complex file paths and command arguments
- Privilege escalation: Support for sudo and other privilege escalation methods
- Timeout configuration: Optional command timeout (default 0 = no timeout)
Use Cases
- Application deployment: Deploy applications directly into containers
- Container configuration: Configure running containers with Ansible tasks
- Multi-container orchestration: Manage complex containerized applications
- Development environments: Set up and configure development containers
- Testing and CI/CD: Run tests and build processes inside containers
- Container image customization: Modify and configure container images using Buildah
Installation and Setup
Prerequisites
- Ansible 2.9 or later
- Podman 3.0+ (for Podman connection plugin)
- Buildah 1.20+ (for Buildah connection plugin)
- Python 3.6+ on the Ansible control node
Installation
# Install from Ansible Galaxy
ansible-galaxy collection install containers.podman
# Or install from source
git clone https://github.com/containers/ansible-podman-collections.git
cd ansible-podman-collections
ansible-galaxy collection build
ansible-galaxy collection install containers-podman-*.tar.gz
Podman Connection Plugin
The Podman connection plugin (containers.podman.podman) allows Ansible to execute tasks inside running Podman containers.
Podman Connection Plugin Features
- Container lifecycle management: Automatically detect and connect to running containers
- Mount point detection: Optimize file transfers by detecting shared mounts
- Environment variable injection: Pass environment variables from inventory to container
- Connection caching: Cache container information for improved performance
- Multiple execution modes: Support for different container runtime configurations
Podman Connection Plugin Basic Configuration
# inventory.yml
[containers]
my-container
[containers:vars]
ansible_connection=containers.podman.podman
ansible_host=my-container-name
Buildah Connection Plugin
The Buildah connection plugin (containers.podman.buildah) enables Ansible to execute tasks inside Buildah working containers, perfect for container image building and customization.
Buildah Connection Plugin Features
- Working container management: Execute tasks in Buildah working containers
- Auto-commit functionality: Optionally commit changes after task execution
- Working directory support: Set and manage working directories
- Layer optimization: Efficient layer creation during image building
- Build context management: Handle build contexts and file transfers
Buildah Connection Plugin Basic Configuration
# inventory.yml
[buildah_containers]
build-container
[buildah_containers:vars]
ansible_connection=containers.podman.buildah
ansible_host=build-container-name
Configuration Options
Podman Connection Plugin Options
| Option | Type | Default | Description |
|---|---|---|---|
ansible_podman_executable |
string | podman |
Path to podman executable |
ansible_podman_timeout |
int | 0 |
Command execution timeout in seconds (0 = no timeout) |
ansible_podman_extra_env |
dict | {} |
Additional environment variables to inject |
ansible_podman_mount_detection |
bool | true |
Enable mount point detection for file operations |
ansible_podman_ignore_mount_errors |
bool | true |
Continue without mount if detection fails |
Buildah Connection Plugin Options
| Option | Type | Default | Description |
|---|---|---|---|
ansible_buildah_executable |
string | buildah |
Path to buildah executable |
ansible_buildah_timeout |
int | 0 |
Command execution timeout in seconds (0 = no timeout) |
ansible_buildah_extra_env |
dict | {} |
Additional environment variables to inject |
ansible_buildah_mount_detection |
bool | true |
Enable mount point detection for file operations |
ansible_buildah_ignore_mount_errors |
bool | true |
Continue without mount if detection fails |
ansible_buildah_auto_commit |
bool | false |
Automatically commit changes after tasks |
ansible_buildah_working_directory |
string | Working directory for command execution |
Simple Usage Examples
Note: Modules like package, file, and copy require Python inside the target container. For minimal images without Python, prefer raw/command tasks or delegate host-side operations (for example, podman cp). Examples below assume Python is available unless stated otherwise.
Example 1: Basic Container Management
---
- name: Configure web server in container
hosts: webserver_container
vars:
ansible_connection: containers.podman.podman
ansible_host: nginx-container
tasks:
- name: Install additional packages
package:
name: curl
state: present
- name: Create configuration directory
file:
path: /etc/nginx/conf.d
state: directory
mode: '0755'
- name: Copy nginx configuration
copy:
src: nginx.conf
dest: /etc/nginx/conf.d/default.conf
mode: '0644'
notify: restart nginx
handlers:
- name: restart nginx
service:
name: nginx
state: restarted
Example 2: Multi-Container Application
---
- name: Deploy multi-tier application
hosts: localhost
gather_facts: false
tasks:
- name: Start database container
containers.podman.podman_container:
name: app-database
image: postgres:13
state: started
env:
POSTGRES_DB: myapp
POSTGRES_USER: appuser
POSTGRES_PASSWORD: secret
ports:
- "5432:5432"
- name: Start application container
containers.podman.podman_container:
name: app-server
image: python:3.9
state: started
command: sleep infinity
ports:
- "8080:8080"
- name: Configure database
hosts: app-database
vars:
ansible_connection: containers.podman.podman
ansible_host: app-database
tasks:
- name: Create application database
postgresql_db:
name: myapp
state: present
- name: Configure application
hosts: app-server
vars:
ansible_connection: containers.podman.podman
ansible_host: app-server
tasks:
- name: Install application dependencies
pip:
requirements: /app/requirements.txt
- name: Start application service
shell: |
cd /app
python app.py
async: 3600
poll: 0
Example 3: Simple Image Building with Buildah
---
- name: Build custom image with Buildah
hosts: localhost
gather_facts: false
tasks:
- name: Create working container
shell: buildah from --name custom-build alpine:latest
- name: Configure the image
hosts: custom-build
vars:
ansible_connection: containers.podman.buildah
ansible_host: custom-build
ansible_buildah_auto_commit: true
tasks:
- name: Update package index
apk:
update_cache: true
- name: Install required packages
apk:
name:
- python3
- py3-pip
- curl
state: present
- name: Create application user
user:
name: appuser
shell: /bin/sh
home: /app
create_home: true
- name: Set working directory
file:
path: /app
state: directory
owner: appuser
group: appuser
- name: Commit custom image with Buildah
hosts: localhost
gather_facts: false
tasks:
- name: Commit the image
shell: buildah commit custom-build custom-alpine:latest
- name: Clean up working container
shell: buildah rm custom-build
Advanced Usage Examples
Example 1: High-Performance Web Application Deployment
---
- name: Deploy high-performance web application
hosts: localhost
gather_facts: false
vars:
app_replicas: 3
load_balancer_image: "nginx:alpine"
app_image: "python:3.9-slim"
tasks:
- name: Create application network
containers.podman.podman_network:
name: app-network
state: present
- name: Start application containers
containers.podman.podman_container:
name: "app-{{ item }}"
image: "{{ app_image }}"
state: started
command: sleep infinity
networks:
- name: app-network
env:
FLASK_ENV: production
WORKERS: "4"
PORT: "5000"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
interval: 30s
timeout: 10s
retries: 3
loop: "{{ range(1, app_replicas + 1) | list }}"
- name: Start load balancer
containers.podman.podman_container:
name: load-balancer
image: "{{ load_balancer_image }}"
state: started
ports:
- "80:80"
- "443:443"
networks:
- name: app-network
volumes:
- nginx-config:/etc/nginx/conf.d:ro
- ssl-certs:/etc/ssl/certs:ro
- name: Configure application containers
hosts: app_containers
vars:
ansible_connection: containers.podman.podman
ansible_podman_extra_env:
PYTHONPATH: "/app"
LOG_LEVEL: "INFO"
ansible_podman_timeout: 60
tasks:
- name: Install system dependencies
apt:
name:
- build-essential
- python3-dev
- libpq-dev
state: present
update_cache: true
- name: Create application directory structure
file:
path: "{{ item }}"
state: directory
mode: '0755'
loop:
- /app
- /app/logs
- /app/static
- /app/templates
- name: Copy application code
copy:
src: "{{ item.src }}"
dest: "{{ item.dest }}"
mode: "{{ item.mode | default('0644') }}"
loop:
- { src: "app.py", dest: "/app/app.py", mode: "0755" }
- { src: "requirements.txt", dest: "/app/requirements.txt" }
- { src: "config.py", dest: "/app/config.py" }
- { src: "wsgi.py", dest: "/app/wsgi.py" }
notify: restart application
- name: Install Python dependencies
pip:
requirements: /app/requirements.txt
virtualenv: /app/venv
virtualenv_python: python3
- name: Configure application settings
template:
src: app_config.j2
dest: /app/.env
mode: '0600'
notify: restart application
- name: Set up log rotation
copy:
content: |
/app/logs/*.log {
daily
missingok
rotate 30
compress
delaycompress
notifempty
copytruncate
}
dest: /etc/logrotate.d/app
mode: '0644'
- name: Start application with gunicorn
shell: |
cd /app
source venv/bin/activate
gunicorn --bind 0.0.0.0:5000 --workers 4 wsgi:app
async: 3600
poll: 0
register: app_service
handlers:
- name: restart application
shell: pkill -f gunicorn || true
- name: Configure load balancer
hosts: load-balancer
vars:
ansible_connection: containers.podman.podman
ansible_podman_timeout: 30
tasks:
- name: Generate nginx upstream configuration
template:
src: nginx_upstream.conf.j2
dest: /etc/nginx/conf.d/upstream.conf
mode: '0644'
notify: reload nginx
- name: Configure SSL certificates
copy:
src: "{{ item.src }}"
dest: "{{ item.dest }}"
mode: '0600'
loop:
- { src: "ssl/app.crt", dest: "/etc/ssl/certs/app.crt" }
- { src: "ssl/app.key", dest: "/etc/ssl/private/app.key" }
notify: reload nginx
- name: Configure nginx main site
template:
src: nginx_site.conf.j2
dest: /etc/nginx/conf.d/default.conf
mode: '0644'
notify: reload nginx
handlers:
- name: reload nginx
service:
name: nginx
state: reloaded
Example 2: Advanced Image Building Pipeline
---
- name: Advanced container image building pipeline
hosts: localhost
gather_facts: false
vars:
base_image: "alpine:3.18"
build_args:
VERSION: "1.0.0"
BUILD_DATE: "{{ ansible_date_time.iso8601 }}"
COMMIT_SHA: "{{ lookup('env', 'GITHUB_SHA') | default('local') }}"
tasks:
- name: Create build context directory
file:
path: "{{ item }}"
state: directory
mode: '0755'
loop:
- /tmp/build-context
- /tmp/build-context/src
- /tmp/build-context/config
- /tmp/build-context/scripts
- name: Copy build files
copy:
src: "{{ item.src }}"
dest: "{{ item.dest }}"
mode: "{{ item.mode | default('0644') }}"
loop:
- { src: "src/", dest: "/tmp/build-context/src/", mode: "0755" }
- { src: "config/", dest: "/tmp/build-context/config/" }
- { src: "scripts/", dest: "/tmp/build-context/scripts/", mode: "0755" }
- { src: "requirements.txt", dest: "/tmp/build-context/" }
- { src: "Dockerfile", dest: "/tmp/build-context/" }
- name: Create multi-stage build containers
shell: |
buildah from --name build-stage {{ base_image }}
buildah from --name runtime-stage {{ base_image }}
register: build_containers
- name: Mount build context
shell: |
buildah copy build-stage /tmp/build-context /build
buildah config --workingdir /build build-stage
- name: Configure build stage
hosts: build-stage
vars:
ansible_connection: containers.podman.buildah
ansible_host: build-stage
ansible_buildah_working_dir: /build
ansible_buildah_extra_env:
BUILD_MODE: "production"
PYTHONPATH: "/build/src"
ansible_buildah_timeout: 300
tasks:
- name: Install build dependencies
apk:
name:
- python3
- py3-pip
- python3-dev
- gcc
- musl-dev
- libffi-dev
- openssl-dev
- make
- git
state: present
update_cache: true
- name: Create virtual environment
shell: python3 -m venv /build/venv
- name: Upgrade pip and install build tools
pip:
name:
- pip
- wheel
- setuptools
state: latest
virtualenv: /build/venv
- name: Install Python dependencies
pip:
requirements: /build/requirements.txt
virtualenv: /build/venv
- name: Compile application
shell: |
source /build/venv/bin/activate
python setup.py build_ext --inplace
python -m compileall src/
args:
chdir: /build
- name: Run tests
shell: |
source /build/venv/bin/activate
python -m pytest tests/ -v
args:
chdir: /build
- name: Create distribution package
shell: |
source /build/venv/bin/activate
python setup.py sdist bdist_wheel
args:
chdir: /build
- name: Prepare runtime artifacts
shell: |
mkdir -p /build/runtime-artifacts
cp -r src/ /build/runtime-artifacts/
cp -r venv/ /build/runtime-artifacts/
cp requirements.txt /build/runtime-artifacts/
cp scripts/entrypoint.sh /build/runtime-artifacts/
args:
chdir: /build
- name: Configure runtime stage
hosts: runtime-stage
vars:
ansible_connection: containers.podman.buildah
ansible_host: runtime-stage
ansible_buildah_working_dir: /app
ansible_buildah_extra_env:
PYTHONPATH: "/app/src"
FLASK_ENV: "production"
ansible_buildah_auto_commit: false
tasks:
- name: Install runtime dependencies only
apk:
name:
- python3
- py3-pip
- curl
- ca-certificates
state: present
update_cache: true
- name: Create application user
user:
name: appuser
shell: /bin/sh
home: /app
create_home: true
system: true
- name: Create application directories
file:
path: "{{ item }}"
state: directory
owner: appuser
group: appuser
mode: '0755'
loop:
- /app
- /app/logs
- /app/data
- /app/tmp
- name: Copy runtime artifacts from build stage
shell: |
buildah copy --from build-stage runtime-stage /build/runtime-artifacts /app
- name: Set proper ownership
file:
path: /app
owner: appuser
group: appuser
recurse: true
- name: Configure entrypoint
shell: |
buildah config --user appuser runtime-stage
buildah config --workingdir /app runtime-stage
buildah config --entrypoint '["./entrypoint.sh"]' runtime-stage
buildah config --cmd '["python", "src/app.py"]' runtime-stage
- name: Set labels
shell: |
buildah config --label version="{{ build_args.VERSION }}" runtime-stage
buildah config --label build-date="{{ build_args.BUILD_DATE }}" runtime-stage
buildah config --label commit-sha="{{ build_args.COMMIT_SHA }}" runtime-stage
buildah config --label maintainer="DevOps Team" runtime-stage
- name: Configure health check
shell: |
buildah config --healthcheck 'CMD curl -f http://localhost:5000/health || exit 1' runtime-stage
buildah config --healthcheck-interval 30s runtime-stage
buildah config --healthcheck-timeout 10s runtime-stage
buildah config --healthcheck-retries 3 runtime-stage
- name: Finalize and tag image
hosts: localhost
gather_facts: false
tasks:
- name: Commit final image
shell: |
buildah commit runtime-stage myapp:{{ build_args.VERSION }}
buildah commit runtime-stage myapp:latest
- name: Tag for registry
shell: |
buildah tag myapp:{{ build_args.VERSION }} registry.example.com/myapp:{{ build_args.VERSION }}
buildah tag myapp:latest registry.example.com/myapp:latest
when: lookup('env', 'REGISTRY_PUSH') == 'true'
- name: Push to registry
shell: |
buildah push registry.example.com/myapp:{{ build_args.VERSION }}
buildah push registry.example.com/myapp:latest
when: lookup('env', 'REGISTRY_PUSH') == 'true'
- name: Clean up build containers
shell: |
buildah rm build-stage runtime-stage
- name: Clean up build context
file:
path: /tmp/build-context
state: absent
- name: Display image information
shell: podman inspect myapp:{{ build_args.VERSION }}
register: image_info
- name: Show build summary
debug:
msg: |
Build completed successfully!
Image: myapp:{{ build_args.VERSION }}
Size: {{ (image_info.stdout | from_json)[0].Size | human_readable }}
Created: {{ (image_info.stdout | from_json)[0].Created }}
Labels: {{ (image_info.stdout | from_json)[0].Config.Labels }}
Example 3: Container Development Environment
---
- name: Set up development environment
hosts: localhost
gather_facts: false
vars:
dev_containers:
- name: dev-database
image: postgres:13
ports: ["5432:5432"]
env:
POSTGRES_DB: devdb
POSTGRES_USER: developer
POSTGRES_PASSWORD: devpass
- name: dev-redis
image: redis:alpine
ports: ["6379:6379"]
- name: dev-workspace
image: ubuntu:22.04
command: sleep infinity
ports: ["8080:8080", "3000:3000"]
volumes:
- "${PWD}:/workspace"
tasks:
- name: Create development network
containers.podman.podman_network:
name: dev-network
state: present
- name: Start development containers
containers.podman.podman_container:
name: "{{ item.name }}"
image: "{{ item.image }}"
state: started
command: "{{ item.command | default(omit) }}"
ports: "{{ item.ports | default([]) }}"
env: "{{ item.env | default({}) }}"
volumes: "{{ item.volumes | default([]) }}"
networks:
- name: dev-network
loop: "{{ dev_containers }}"
- name: Configure development workspace
hosts: dev-workspace
vars:
ansible_connection: containers.podman.podman
ansible_host: dev-workspace
ansible_podman_extra_env:
TERM: "xterm-256color"
EDITOR: "vim"
ansible_podman_timeout: 120
tasks:
- name: Update package cache
apt:
update_cache: true
- name: Install development tools
apt:
name:
- curl
- wget
- git
- vim
- tmux
- zsh
- python3
- python3-pip
- nodejs
- npm
- build-essential
- postgresql-client
- redis-tools
state: present
- name: Install Python development packages
pip:
name:
- ipython
- jupyter
- black
- flake8
- pytest
- requests
- sqlalchemy
- redis
state: present
- name: Configure Git
git_config:
name: "{{ item.name }}"
value: "{{ item.value }}"
scope: global
loop:
- { name: "user.name", value: "Developer" }
- { name: "user.email", value: "dev@example.com" }
- { name: "init.defaultBranch", value: "main" }
- name: Set up shell environment
copy:
content: |
export PATH=$PATH:/workspace/bin
export PYTHONPATH=/workspace/src
export DATABASE_URL=postgresql://developer:devpass@dev-database:5432/devdb
export REDIS_URL=redis://dev-redis:6379/0
alias ll='ls -la'
alias ..='cd ..'
alias gs='git status'
alias gd='git diff'
alias gc='git commit'
alias gp='git push'
# Auto-activate virtual environment if it exists
if [ -d "/workspace/venv" ]; then
source /workspace/venv/bin/activate
fi
dest: /root/.bashrc
mode: '0644'
- name: Create workspace directories
file:
path: "{{ item }}"
state: directory
mode: '0755'
loop:
- /workspace/src
- /workspace/tests
- /workspace/docs
- /workspace/bin
- /workspace/config
- name: Install oh-my-zsh
shell: |
sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" || true
chsh -s $(which zsh)
ignore_errors: true
- name: Start Jupyter notebook server
shell: |
cd /workspace
nohup jupyter notebook --ip=0.0.0.0 --port=8080 --no-browser --allow-root &
async: 10
poll: 0
ignore_errors: true
- name: Verify development environment
hosts: localhost
gather_facts: false
tasks:
- name: Test database connection
shell: |
podman exec dev-workspace psql postgresql://developer:devpass@dev-database:5432/devdb -c "SELECT version();"
register: db_test
ignore_errors: true
- name: Test Redis connection
shell: |
podman exec dev-workspace redis-cli -h dev-redis ping
register: redis_test
ignore_errors: true
- name: Display environment status
debug:
msg: |
Development environment ready!
Services:
- Database: {{ 'OK' if db_test.rc == 0 else 'FAILED' }}
- Redis: {{ 'OK' if redis_test.rc == 0 else 'FAILED' }}
- Workspace: Running
Access:
- Jupyter: http://localhost:8080
- Workspace: podman exec -it dev-workspace /bin/bash
- Database: psql postgresql://developer:devpass@localhost:5432/devdb
Workspace mounted at: /workspace
Performance and Best Practices
Connection Caching
Both plugins internally cache lightweight details (for example, container validation) to reduce overhead. There are no user-facing options to control this.
Mount Detection
Enable mount detection for efficient file transfers:
# Automatically detect shared mounts (default: true)
ansible_podman_mount_detection: true
ansible_buildah_mount_detection: true
# Ignore mount detection errors
ansible_podman_ignore_mount_errors: true
ansible_buildah_ignore_mount_errors: true
Timeout Configuration
Configure optional timeouts (default is 0 = no timeout):
# Command timeout in seconds (0 = no timeout)
ansible_podman_timeout: 0
ansible_buildah_timeout: 0
Environment Variable Management
Efficiently pass environment variables:
# Method 1: Individual variables
ansible_podman_extra_env:
API_KEY: "{{ vault_api_key }}"
DEBUG: "true"
LOG_LEVEL: "INFO"
# Method 2: From file
ansible_podman_extra_env: "{{ lookup('file', 'env.json') | from_json }}"
Best Practices
- Use specific container names: Avoid generic names to prevent conflicts
- Implement health checks: Use container health checks for reliability
- Handle secrets securely: Use Ansible Vault for sensitive data
- Monitor resource usage: Set appropriate memory and CPU limits
- Use multi-stage builds: Optimize image size with Buildah multi-stage builds
- Cache dependencies: Leverage layer caching for faster builds
- Design for fail-fast: Prefer explicit failed_when/changed_when instead of retries
Troubleshooting
Common Issues
Container Not Found
Error: Container 'my-container' not found
Solution: Ensure the container is running and the name/ID is correct.
Permission Denied
Error: Permission denied when accessing container
Solution: Check container permissions or run with appropriate privileges.
Timeout Errors
Error: Command execution timed out
Solution: Increase timeout values or optimize commands.
Mount Detection Failures
Warning: Mount detection failed
Solution: Disable mount detection or ignore mount errors.
Debug Configuration
Enable verbose logging for troubleshooting:
# Enable debug logging
ansible_podman_debug: true
ansible_buildah_debug: true
# Increase verbosity
ansible_verbosity: 3
Testing Connection
Test connection plugin functionality:
# Test Podman connection
ansible -i inventory -m setup podman_host
# Test Buildah connection
ansible -i inventory -m setup buildah_host
# Run connection tests
./ci/run_connection_test.sh podman
./ci/run_connection_test.sh buildah
Migration from Legacy Plugins
From Docker Connection Plugin
Replace Docker connection configuration:
# Old Docker configuration
ansible_connection: docker
ansible_docker_extra_args: "--user root"
# New Podman configuration
ansible_connection: containers.podman.podman
ansible_podman_extra_env:
USER: root
From Custom Scripts
Replace custom container execution scripts:
# Old custom script approach
- name: Run in container
shell: podman exec my-container {{ command }}
# New connection plugin approach
- name: Run in container
hosts: my-container
vars:
ansible_connection: containers.podman.podman
tasks:
- name: Execute command
command: "{{ command }}"
Configuration Mapping
| Legacy Option | Podman Plugin | Buildah Plugin |
|---|---|---|
ansible_docker_extra_args |
ansible_podman_extra_env |
ansible_buildah_extra_env |
ansible_docker_timeout |
ansible_podman_timeout |
ansible_buildah_timeout |
| N/A | ansible_podman_retries |
ansible_buildah_retries |
| N/A | ansible_podman_mount_detection |
ansible_buildah_mount_detection |
For more information and examples, see the official documentation and test cases.