Ghost Hands – Automating The Stronghold With Ansible And Podman

Mission Update

Now we automate.

The pod.
The proxy.
The secrets.

One playbook to conjure the setup, one command to repeat the ritual.

Requirements

  • Ansible (core + community.containers)
  • Podman configured for rootless ops
  • X509 certs or automation for Let’s Encrypt
  • Directory structure with:
    • roles/semaphore/tasks/main.yml
    • files/nginx.conf
    • files/cert.pem, key.pem (or auto-gen task)

Playbook Strategy

  • Create Podman pod
  • Deploy MySQL container
  • Deploy Semaphore UI container
  • Deploy NginX container with volumes mounted

Secret Externalization

Store credentials in group_vars/containers.yml and encrypt with Ansible Vault.

Step 1: Create The Pod

- name: Create the pod
  containers.podman.podman_pod:
    name: p_semaphore
    state: created
    ports:
      - "4430:443"

Step 2: Activate the MySQL Container

- name: Create the MySQL container
  containers.podman.podman_container:
    name: semaphore_mysql
    pod: p_semaphore
    state: started
    rm: true
    detach: true
    image: docker.io/mysql:lts
    volumes:
      - semaphore_mysql:/var/lib/mysql
    env:
      MYSQL_RANDOM_ROOT_PASSWORD: 'yes'
      MYSQL_DATABASE: semaphore
      MYSQL_USER: semaphore
      MYSQL_PASSWORD: "{{ semaphore_mysql_pw }}"

Step 3: Initiate the Semaphore container

- name: Create the Semaphore container
  containers.podman.podman_container:
    name: semaphore
    pod: p_semaphore
    state: started
    rm: true
    detach: true
    image: docker.io/semaphoreui/semaphore:latest
    env:
      SEMAPHORE_DB_USER: semaphore
      SEMAPHORE_DB_PASS: "{{ semaphore_mysql_pw }}"
      SEMAPHORE_DB_HOST: 127.0.0.1
      SEMAPHORE_DB_PORT: 3306
      SEMAPHORE_DB_DIALECT: mysql
      SEMAPHORE_DB: semaphore
      SEMAPHORE_PLAYBOOK_PATH: /tmp/semaphore/
      SEMAPHORE_ADMIN_PASSWORD: "{{ semaphore_admin_pw }}"
      SEMAPHORE_ADMIN_NAME: admin
      SEMAPHORE_ADMIN_EMAIL: admin@localhost
      SEMAPHORE_ADMIN: admin
      SEMAPHORE_ACCESS_KEY_ENCRYPTION: gs72mPntFATGJs9qK0pQ0rKtfidlexiMjYCH9gWKhTU=
      SEMAPHORE_LDAP_ACTIVATED: 'no'
      TZ: UTC

Step 4: The NginX Reverse Proxy

- name: Create the NginX container
  containers.podman.podman_container:
    name: semaphore_proxy
    pod: p_semaphore
    state: started
    rm: true
    detach: true
    image: docker.io/library/nginx
    volumes:
      - "{{ role_path }}/files/nginx.conf:/etc/nginx/conf.d/default.conf:ro"
      - "{{ role_path }}/files/cert.pem:/etc/nginx/cert.pem:ro"
      - "{{ role_path }}/files/key.pem:/etc/nginx/key.pem:ro"

Step 5: Verify

Log in.
Verify the configuration.
Monitor the logs with podman pod logs p_semaphore.

Final Signal

The stronghold stands.
Provisioned in silence. Hardened by code.

But this is just the surface. A faint signal.
There is much to do for a strong signal.

Need the full Ansible role?

  • Variables ready
  • Templates in place
  • Handlers sharpened
  • Deploy once, repeat forever

Buy the hardened role + full guide – forged in the silence.

Whisper to DeadSwitch on Matrix: @deadswitch:matrix.org
Maybe the Ghost signals back.

DeadSwitch | The Silent Architect
In silence, I rise. In structure, I endure.

Leave a comment