AWX (Ansible Tower)

Automation rootless 4 containers
awx-web quay.io/ansible/awx:latest · 8052:8052
awx-task quay.io/ansible/awx:latest
postgres docker.io/postgres:15
redis docker.io/redis:7-alpine

Kubernetes YAML

apiVersion: v1
kind: Pod
metadata:
  name: awx
  labels:
    app: awx
spec:
  restartPolicy: Always
  containers:
  - name: awx-web
    image: quay.io/ansible/awx:latest
    imagePullPolicy: Always
    ports:
    - containerPort: 8052
      hostPort: 8052
    env:
    - name: DATABASE_USER
      value: awx
    - name: DATABASE_PASSWORD
      value: IdXf8mOflAcfp_POzik4OA
    - name: DATABASE_NAME
      value: awx
    - name: DATABASE_HOST
      value: 127.0.0.1
    - name: DATABASE_PORT
      value: '5432'
    - name: REDIS_HOST
      value: 127.0.0.1
    - name: REDIS_PORT
      value: '6379'
    - name: SECRET_KEY
      value: ocABFzu5DW8SVQJkTrdRbA
    - name: AWX_ADMIN_USER
      value: admin
    - name: AWX_ADMIN_PASSWORD
      value: IdXf8mOflAcfp_POzik4OA
    volumeMounts:
    - name: vol-0
      mountPath: /etc/tower
    - name: vol-1
      mountPath: /etc/nginx
    - name: vol-2
      mountPath: /var/lib/awx/projects
    command:
    - /bin/sh
    args:
    - -c
    - until python3 -c "import socket;s=socket.create_connection(('${DATABASE_HOST:-127.0.0.1}',${DATABASE_PORT:-5432}),1);s.close()"
      2>/dev/null; do echo 'Waiting for postgres...'; sleep 3; done && awx-manage
      migrate && DJANGO_SUPERUSER_PASSWORD=${AWX_ADMIN_PASSWORD:-IdXf8mOflAcfp_POzik4OA}
      awx-manage createsuperuser --noinput --username ${AWX_ADMIN_USER:-admin} --email
      admin@localhost 2>/dev/null || true && exec /usr/bin/launch_awx_web.sh
  - name: awx-task
    image: quay.io/ansible/awx:latest
    imagePullPolicy: Always
    env:
    - name: DATABASE_USER
      value: awx
    - name: DATABASE_PASSWORD
      value: IdXf8mOflAcfp_POzik4OA
    - name: DATABASE_NAME
      value: awx
    - name: DATABASE_HOST
      value: 127.0.0.1
    - name: DATABASE_PORT
      value: '5432'
    - name: REDIS_HOST
      value: 127.0.0.1
    - name: REDIS_PORT
      value: '6379'
    - name: SECRET_KEY
      value: ocABFzu5DW8SVQJkTrdRbA
    - name: AWX_ADMIN_USER
      value: admin
    - name: AWX_ADMIN_PASSWORD
      value: IdXf8mOflAcfp_POzik4OA
    volumeMounts:
    - name: vol-0
      mountPath: /etc/tower
    - name: vol-2
      mountPath: /var/lib/awx/projects
    command:
    - /usr/bin/launch_awx_task.sh
  - name: postgres
    image: docker.io/postgres:15
    env:
    - name: POSTGRES_USER
      value: awx
    - name: POSTGRES_PASSWORD
      value: IdXf8mOflAcfp_POzik4OA
    - name: POSTGRES_DB
      value: awx
    volumeMounts:
    - name: vol-3
      mountPath: /var/lib/postgresql/data
    securityContext:
      runAsUser: 999
      runAsGroup: 999
  - name: redis
    image: docker.io/redis:7-alpine
    volumeMounts:
    - name: vol-4
      mountPath: /data
    args:
    - redis-server
    - --appendonly
    - 'yes'
  initContainers:
  # Auto-generated init container — do not edit manually.
  # settings.py: required by AWX to read DB/Redis credentials from environment variables above.
  # nginx.conf: reverse proxy config for the AWX web UI (port 8052 -> uWSGI on 8050).
  # uwsgi.ini: copied directly from the AWX image — updates automatically with new image versions.
  - name: awx-init
    image: quay.io/ansible/awx:latest
    imagePullPolicy: Always
    volumeMounts:
    - name: vol-0
      mountPath: /init-tower
    - name: vol-1
      mountPath: /init-nginx
    command:
    - sh
    args:
    - -c
    - cp -r /etc/nginx/. /init-nginx/ && echo aW1wb3J0IG9zCgpEQVRBQkFTRVMgPSB7CiAgICAnZGVmYXVsdCc6IHsKICAgICAgICAnQVRPTUlDX1JFUVVFU1RTJzogVHJ1ZSwKICAgICAgICAnRU5HSU5FJzogJ2F3eC5tYWluLmRiLnByb2ZpbGVkX3BnJywKICAgICAgICAnTkFNRSc6IG9zLmVudmlyb24uZ2V0KCdEQVRBQkFTRV9OQU1FJywgJ2F3eCcpLAogICAgICAgICdVU0VSJzogb3MuZW52aXJvbi5nZXQoJ0RBVEFCQVNFX1VTRVInLCAnYXd4JyksCiAgICAgICAgJ1BBU1NXT1JEJzogb3MuZW52aXJvbi5nZXQoJ0RBVEFCQVNFX1BBU1NXT1JEJywgJ2NoYW5nZW1lJyksCiAgICAgICAgJ0hPU1QnOiBvcy5lbnZpcm9uLmdldCgnREFUQUJBU0VfSE9TVCcsICcxMjcuMC4wLjEnKSwKICAgICAgICAnUE9SVCc6IG9zLmVudmlyb24uZ2V0KCdEQVRBQkFTRV9QT1JUJywgJzU0MzInKSwKICAgIH0KfQpBTExPV0VEX0hPU1RTID0gWycqJ10KU0VDUkVUX0tFWSA9IG9zLmVudmlyb24uZ2V0KCdTRUNSRVRfS0VZJywgJ2NoYW5nZW1lJykKREpBTkdPX1JFRElTX0lHTk9SRV9FWENFUFRJT05TID0gVHJ1ZQoKX3JlZGlzX2hvc3QgPSBvcy5lbnZpcm9uLmdldCgnUkVESVNfSE9TVCcsICcxMjcuMC4wLjEnKQpfcmVkaXNfcG9ydCA9IG9zLmVudmlyb24uZ2V0KCdSRURJU19QT1JUJywgJzYzNzknKQpfcmVkaXNfdXJsID0gJ3JlZGlzOi8ve306e30nLmZvcm1hdChfcmVkaXNfaG9zdCwgX3JlZGlzX3BvcnQpCgpDQUNIRVMgPSB7CiAgICAnZGVmYXVsdCc6IHsKICAgICAgICAnQkFDS0VORCc6ICdhd3gubWFpbi5jYWNoZS5BV1hSZWRpc0NhY2hlJywKICAgICAgICAnTE9DQVRJT04nOiAne30vMScuZm9ybWF0KF9yZWRpc191cmwpLAogICAgfQp9CkNIQU5ORUxfTEFZRVJTID0gewogICAgJ2RlZmF1bHQnOiB7CiAgICAgICAgJ0JBQ0tFTkQnOiAnY2hhbm5lbHNfcmVkaXMuY29yZS5SZWRpc0NoYW5uZWxMYXllcicsCiAgICAgICAgJ0NPTkZJRyc6IHsKICAgICAgICAgICAgJ2hvc3RzJzogWyhfcmVkaXNfaG9zdCwgaW50KF9yZWRpc19wb3J0KSldLAogICAgICAgICAgICAnY2FwYWNpdHknOiAxMDAwMCwKICAgICAgICAgICAgJ2dyb3VwX2V4cGlyeSc6IDE1Nzc4NDc2MCwKICAgICAgICB9CiAgICB9Cn0KQlJPS0VSX1VSTCA9IF9yZWRpc191cmwK
      | base64 -d > /init-tower/settings.py && cp /etc/tower/uwsgi.ini /init-tower/uwsgi.ini
      && echo d29ya2VyX3Byb2Nlc3NlcyBhdXRvOwplcnJvcl9sb2cgL2Rldi9zdGRlcnIgd2FybjsKcGlkIC90bXAvbmdpbngucGlkOwoKaW5jbHVkZSAvdXNyL3NoYXJlL25naW54L21vZHVsZXMvKi5jb25mOwoKZXZlbnRzIHsKICAgIHdvcmtlcl9jb25uZWN0aW9ucyAxMDI0Owp9CgpodHRwIHsKICAgIGNsaWVudF9ib2R5X3RlbXBfcGF0aCAvdG1wL25naW54LWNsaWVudC1ib2R5OwogICAgcHJveHlfdGVtcF9wYXRoIC90bXAvbmdpbngtcHJveHk7CiAgICBmYXN0Y2dpX3RlbXBfcGF0aCAvdG1wL25naW54LWZhc3RjZ2k7CiAgICB1d3NnaV90ZW1wX3BhdGggL3RtcC9uZ2lueC11d3NnaTsKICAgIHNjZ2lfdGVtcF9wYXRoIC90bXAvbmdpbngtc2NnaTsKICAgIGluY2x1ZGUgL2V0Yy9uZ2lueC9taW1lLnR5cGVzOwogICAgZGVmYXVsdF90eXBlIGFwcGxpY2F0aW9uL29jdGV0LXN0cmVhbTsKICAgIGFjY2Vzc19sb2cgL2Rldi9zdGRvdXQ7CiAgICBzZW5kZmlsZSBvbjsKICAgIGtlZXBhbGl2ZV90aW1lb3V0IDY1OwoKICAgIHNlcnZlciB7CiAgICAgICAgbGlzdGVuIDgwNTIgZGVmYXVsdF9zZXJ2ZXI7CiAgICAgICAgc2VydmVyX25hbWUgXzsKICAgICAgICBjbGllbnRfbWF4X2JvZHlfc2l6ZSAwOwoKICAgICAgICBsb2NhdGlvbiB+IF4vd2Vic29ja2V0IHsKICAgICAgICAgICAgcHJveHlfcGFzcyBodHRwOi8vMTI3LjAuMC4xOjgwNTE7CiAgICAgICAgICAgIHByb3h5X2h0dHBfdmVyc2lvbiAxLjE7CiAgICAgICAgICAgIHByb3h5X3NldF9oZWFkZXIgVXBncmFkZSAkaHR0cF91cGdyYWRlOwogICAgICAgICAgICBwcm94eV9zZXRfaGVhZGVyIENvbm5lY3Rpb24gIlVwZ3JhZGUiOwogICAgICAgICAgICBwcm94eV9jb25uZWN0X3RpbWVvdXQgNzVzOwogICAgICAgICAgICBwcm94eV9yZWFkX3RpbWVvdXQgMTIwczsKICAgICAgICB9CgogICAgICAgIGxvY2F0aW9uIC9zdGF0aWMvIHsKICAgICAgICAgICAgYWxpYXMgL3Zhci9saWIvYXd4L3B1YmxpYy9zdGF0aWMvOwogICAgICAgIH0KCiAgICAgICAgbG9jYXRpb24gLyB7CiAgICAgICAgICAgIHV3c2dpX3JlYWRfdGltZW91dCAxMjBzOwogICAgICAgICAgICB1d3NnaV9wYXNzIDEyNy4wLjAuMTo4MDUwOwogICAgICAgICAgICBpbmNsdWRlIC9ldGMvbmdpbngvdXdzZ2lfcGFyYW1zOwogICAgICAgICAgICBwcm94eV9zZXRfaGVhZGVyIFgtRm9yd2FyZGVkLUZvciAkcHJveHlfYWRkX3hfZm9yd2FyZGVkX2ZvcjsKICAgICAgICAgICAgdXdzZ2lfcGFyYW0gSFRUUF9YX0ZPUldBUkRFRF9GT1IgJHByb3h5X2FkZF94X2ZvcndhcmRlZF9mb3I7CiAgICAgICAgfQogICAgfQp9Cg==
      | base64 -d > /init-nginx/nginx.conf
  volumes:
  - name: vol-0
    persistentVolumeClaim:
      claimName: awx-tower
  - name: vol-1
    persistentVolumeClaim:
      claimName: awx-nginx
  - name: vol-2
    persistentVolumeClaim:
      claimName: awx-projects
  - name: vol-3
    persistentVolumeClaim:
      claimName: awx-db
  - name: vol-4
    persistentVolumeClaim:
      claimName: awx-redis

Deployment Guide rootless

Operating System:
!

Prerequisites once as root

0. Install Podman

apt update && apt install -y podman

1. Create user (if not existing)

useradd -m -s /bin/bash 
passwd 

2. Enable linger (service runs after reboot without login)

loginctl enable-linger 

Switch to deployment user

su - 

Use su - with the dash — this creates a proper login shell as . Without the dash, root environment variables carry over and rootless Podman will not work correctly.

As deployment user

!

Fix environment for rootless systemd (new users only — skip if already set)

Required once so systemctl --user works in non-login sessions.

cat << 'EOF' >> ~/.bashrc

# Fix for systemd user services (rootless Podman/systemctl)
export XDG_RUNTIME_DIR=/run/user/$(id -u)
export DBUS_SESSION_BUS_ADDRESS=unix:path=$XDG_RUNTIME_DIR/bus
EOF
source ~/.bashrc
1

Save the YAML file

mkdir -p ~/.config/containers/
nano ~/.config/containers/awx.yaml
2

Test the pod (without autostart)

podman play kube ~/.config/containers/awx.yaml

# Check status:
podman pod ps && podman ps

# Stop:
podman play kube --down ~/.config/containers/awx.yaml
3

Create Quadlet .kube file

awx.kube

          

%h is a systemd home-directory specifier — do not replace it with ~/.

Save to ~/.config/containers/systemd/:

mkdir -p ~/.config/containers/systemd/
nano ~/.config/containers/systemd/awx.kube
4

Start systemd service

systemctl --user daemon-reload
systemctl --user start awx.service
5

Status & Logs

systemctl --user status awx.service
journalctl --user -u awx.service -f
podman pod ps
podman ps
6

Enable automatic image updates optional

Requires AutoUpdate=registry in the .kube file. Podman then checks for new images and restarts the pod automatically:

systemctl --user enable --now podman.socket
systemctl --user daemon-reload
systemctl --user enable --now podman-auto-update.timer

# Check status:
systemctl --user status podman-auto-update.timer

# Trigger manually:
podman auto-update

Ports < 1024 (e.g. 80, 443)

Rootless cannot open privileged ports. Solution:

sysctl net.ipv4.ip_unprivileged_port_start=80

Make persistent in /etc/sysctl.d/99-podman.conf.

Containers communicate via localhost

All containers in the pod share the same network namespace. Always use localhost, not container names.

# Correct (e.g. app → db):
localhost:5432

# Wrong (doesn't work in a pod):
db-container:5432

List open ports

Which ports is the running pod listening on?

podman port awx-pod

Custom DNS for the pod

Set a custom DNS server (e.g. local Pi-hole):

# In YAML under spec.dnsConfig:
spec:
  dnsConfig:
    nameservers:
      - 192.168.1.x

Set volume ownership

Fix permission errors by adjusting UID/GID in the user namespace:

podman unshare chown 1000:1000 /path/to/volume

SELinux volume labels

On SELinux systems (RHEL, Fedora) set the volume suffix:

/host/path:/container/path:Z   # private
/host/path:/container/path:z   # shared

List all volumes

podman volume ls
podman volume inspect <volume-name>

Volume backup

Back up data from a named volume:

podman run --rm \
  -v <volume-name>:/data:ro \
  -v $(pwd):/backup \
  busybox tar czf /backup/backup.tar.gz /data

Cleanup

Remove unused images, containers and volumes:

podman system prune -f        # containers + images
podman image prune -f         # untagged images only
podman volume prune -f        # unused volumes

Manual update

Pull a new image version and restart the pod:

podman pull <image>:<tag>
podman play kube --replace \
  ~/.config/containers/awx.yaml

Check for outdated images

List local images and test auto-update without applying:

podman images
podman auto-update --dry-run

Shell into a running container

podman exec -it awx-<container> /bin/sh
# or bash:
podman exec -it awx-<container> /bin/bash

Follow live logs

# All containers in the pod:
podman pod logs -f awx-pod

# Single container:
podman logs -f awx-<container>

Pod info & resource usage

podman pod inspect awx-pod
podman stats awx-pod

Restart pod without data loss

podman pod restart awx-pod

# or via systemd:
systemctl --user restart awx.service

Stop & remove pod

podman pod stop awx-pod
podman pod rm awx-pod

All in one

podman pod stop awx-pod && podman pod rm awx-pod

Customize this stack
Change ports, volumes, environment variables and more in the generator.
Open in Generator