Ansible Setup with Semaphore for LXC's on Proxmox

Ansible Setup with Semaphore for LXC's on Proxmox

A walkthrough for setting up Ansible with Semaphore UI to automate tasks across Proxmox LXC containers, covering SSH key pairs, service accounts, inventory configuration, and your first playbook run.

Industrial pipeline along the Emscher river in Gelsenkirchen-Erle, Germany

Fig. 1. Gelsenkirchen-Erle, pipeline along the Emscher
Source: Adapted from [1]

At some point in your enterprise and homelab journey, you begin to recognize that automating the general maintenance of various systems becomes heavy on time and effort to keep everything in tip-top shape. After SSH’ing into the 5th, 20th, or 50th connection to run the same few commands, you may think to yourself: there has to be a better way. A lot of solutions exist to relieve these pressures. As you get into infrastructure as code, plenty of platforms will pull for your attention, and choosing the right path to automation nirvana matters just as much as getting there. Because of this, I always recommend putting serious effort into researching which automation platform you choose. After all, we don’t want our decisions biting us months or years down the road, reinventing the automation wheel. Keep in mind, the best solution is the one that actually gets implemented in your cost-value analysis.

For my homelab, personal preference carries a lot of weight. How I like to interact with my lab and maintain it, cost, and time investment are all big factors. Combine this with gambling on the future staying power of a platform, and the decisions are not always obvious. When we think of value, we also want to apply our project management skills here. Fads are kryptonite. Available documentation saves time. The size of the community and the potential for a friendly interaction when we need to find assistance are all part of the equation. These are all considerations before you even get to specifications, how the automation works, and so on.

If you value agentless experiences using built-in technologies that are often already available by default and well understood, Ansible is a great addition to any homelab. With the addition of Semaphore UI, we get a more robust, near enterprise solution.

Scope

  • Proxmox: This post assumes a Proxmox environment with LXCs and VMs [4]. We’ll use Proxmox’s built-in binaries to help us create the environment that allows for secure connections between our automation platform and the various endpoints.
  • LXC or VM Endpoints: We need something to run tasks against.
  • Comfort with YAML & JSON: Ansible Playbooks and Semaphore rely on both of these structured text styles.
  • Linux Comfort: We’ll be using a bit of bash, SSH, and more.
  • Semaphore Setup with Ansible: This assumes you can handle Semaphore installation on your own via Docker or LXC. For Semaphore UI Installation see https://semaphoreui.com/docs/admin-guide/installation 🔗

Ansible

Ansible is an automation system for IT infrastructure, executing tasks on nodes, network devices, cloud platforms, and endpoints [2]. Ansible is open source, owned by Red Hat, and supports Linux, Unix, macOS, and Windows. Ansible is agentless. It handles connections and executes CLI commands via SSH. These tasks are written in YAML files called “Playbooks” that act as recipes describing the desired state of your systems: package installations, service configurations, file deployments, and more.

Semaphore UI Community Edition

Semaphore is a modern web UI for running Ansible playbooks, Terraform and OpenTofu plans, PowerShell and Bash scripts, and other automation tasks [3]. The community edition is open source, with Pro and Enterprise versions offering additional features. The limitations of the community edition outside of enterprise scenarios are very reasonable, and it has been around for nearly a decade at this point.

  1. Install Semaphore (Docker or LXC)
  2. Generate an SSH key pair and add the private key to the Semaphore Key Store
  3. Run the onboarding script from your Proxmox host to create the ansible service account on each LXC
  4. Configure Variable Groups in Semaphore for sudo / privilege escalation
  5. Define your inventory of hosts in Semaphore using YAML
  6. Create a git repository to host your Playbooks and connect it to Semaphore
  7. Create a test task and verify everything works

Setup Overview

The diagram below shows how the pieces fit together. Semaphore sits in the middle. It pulls playbooks from your git repository, connects to your LXC endpoints over SSH using the service account we create, and executes tasks with sudo privilege escalation.

flowchart TD
    A["🔧 Install Semaphore<br/><small>Docker or LXC</small>"] --> B["🔑 Generate SSH Key Pair<br/><small>ed25519 for Semaphore</small>"]
    B --> C["🔐 Add Private Key<br/>to Semaphore Key Store"]
    B --> D["🖥️ Run Onboarding Script<br/><small>from Proxmox host</small>"]

    D --> E["📦 OS Detection &<br/>Dependency Install"]
    E --> F["👤 Create ansible<br/>Service Account"]
    F --> G["🔑 Deploy SSH<br/>Public Key"]
    G --> H["🛡️ Configure Sudoers<br/><small>NOPASSWD:ALL</small>"]
    H --> I["🔄 Restart SSH<br/>Service"]

    C --> J["⚙️ Set Variable Groups<br/><small>JSON — sudo / become</small>"]
    J --> K["📋 Define Inventory<br/><small>YAML host groups</small>"]
    K --> L["📂 Create Git Repository<br/><small>& Connect to Semaphore</small>"]

    I --> M["✅ Create Test Task<br/>& Verify"]
    L --> M

    style A fill:#1a1a2e,color:#e0e0e0,stroke:#4a9eff
    style B fill:#1a1a2e,color:#e0e0e0,stroke:#4a9eff
    style C fill:#1a1a2e,color:#e0e0e0,stroke:#4a9eff
    style D fill:#2d1a1a,color:#e0e0e0,stroke:#ff6b6b
    style E fill:#2d1a1a,color:#e0e0e0,stroke:#ff6b6b
    style F fill:#2d1a1a,color:#e0e0e0,stroke:#ff6b6b
    style G fill:#2d1a1a,color:#e0e0e0,stroke:#ff6b6b
    style H fill:#2d1a1a,color:#e0e0e0,stroke:#ff6b6b
    style I fill:#2d1a2e,color:#e0e0e0,stroke:#ff6b6b
    style J fill:#1a2d1a,color:#e0e0e0,stroke:#6bff6b
    style K fill:#1a2d1a,color:#e0e0e0,stroke:#6bff6b
    style L fill:#1a1a2e,color:#e0e0e0,stroke:#4a9eff
    style M fill:#2d2d1a,color:#e0e0e0,stroke:#ffd93d
Diagram Legend
ColorTrack
🔵 Blue borderSemaphore setup (install, keys, git repository)
🔴 Red borderLXC host onboarding (via Proxmox bash script)
🟢 Green borderInventory & variable configuration (in Semaphore)
🟡 Yellow borderVerification — all tracks converge here

Create Service Accounts

The first step is creating the conditions in our clients and environment so we can hook our Semaphore UI to our endpoints. We’ll do this by creating a new user account on our Linux endpoints and providing an authorized key. If you don’t have SSH enabled on your endpoints, you’ll need to set that up first.

Create Public/Private Key Pair

Pull up your Semaphore server terminal. We’ll create a public/private key for use with our Ansible service accounts. This key will be used on each endpoint or LXC to authenticate SSH.

When asked to create a passphrase, leave it blank and hit enter.

Terminal window
ssh-keygen -t ed25519 -C "semaphore-automation" -f ~/.ssh/id_ansible

This creates two files:

  • id_ansible: This is your private key
  • id_ansible.pub: This is your public key

Open the private key and copy the contents to your clipboard, including the header and footer lines exactly as they appear.

Terminal window
nano -w ~/.ssh/id_ansible

Open your Semaphore UI webpage, log in, and click “Key Store” on the left-hand side.

Click the “New Key” button at the top right.

  • Type: SSH Key
  • Username: (leave blank)
  • Passphrase: (leave blank)
  • Private Key: Paste the contents of your clipboard from ~/.ssh/id_ansible

Screenshot of the Keystore For Ansible service account.

Create Ansible Service Accounts on LXCs

First, I’ll walk you through the intent using simple commands for each endpoint LXC. You don’t have to type these commands in. I’ve created a bash script below that can carry out this task on many LXCs.

This is just for your reference.

Terminal window
# 1. Create the user
useradd -m -s /bin/bash ansible
# 2. Setup the .ssh directory
mkdir -p /home/ansible/.ssh
chmod 700 /home/ansible/.ssh
# 3. Add your PUBLIC key (the .pub file) to authorized_keys
# Replace 'YOUR_PUBLIC_KEY_STRING' with the actual text from ~/.ssh/id_ansible.pub
echo "YOUR_PUBLIC_KEY_STRING" > /home/ansible/.ssh/authorized_keys
chmod 600 /home/ansible/.ssh/authorized_keys
chown -R ansible:ansible /home/ansible/.ssh

LXC Onboarding Script

Run this bash script from your Proxmox host against your LXCs. It handles OS detection and creates the ansible service account.

This script deploys your ansible SSH public key, configures passwordless sudo, and restarts SSH all in one pass.

  1. Edit the public key in this script with your own id_ansible.pub contents.
  2. Edit the range of Proxmox LXC IDs you want to onboard.

From your Proxmox host as root:

Terminal window
nano ~/ansible_create_service_accounts_script.sh
#!/bin/bash
# Your Semaphore Public Key
PUB_KEY="ssh-ed25519...YOUR PUBLIC KEY ...semaphore-automation"
# Adjust range as needed for your LXC IDs
for id in {100..110}; do
# Check if the container exists and is running
status=$(pct status $id 2>/dev/null)
if [[ $? -ne 0 || "$status" != *"status: running"* ]]; then
continue
fi
echo "--- Processing LXC $id ---"
pct exec $id -- /bin/sh -c "
# 1. OS Detection & Dependency Install
if [ -f /usr/bin/apk ]; then
echo 'Detected Alpine Linux'
apk add --no-cache sudo shadow openssh-sftp-server openssh
SHELL_BIN='/bin/sh'
SSH_SERVICE='sshd'
elif [ -f /usr/bin/apt ]; then
echo 'Detected Debian/Ubuntu'
apt-get update && apt-get install -y sudo openssh-server
SHELL_BIN='/bin/bash'
SSH_SERVICE='ssh'
else
echo 'Unknown OS, attempting generic setup...'
SHELL_BIN='/bin/sh'
SSH_SERVICE='sshd'
fi
# 2. User Creation & Account Unlocking
if ! id -u ansible >/dev/null 2>&1; then
useradd -m -s \"\$SHELL_BIN\" ansible
fi
# Ensure account is not locked (Common Alpine issue)
if [ -f /etc/shadow ]; then
passwd -u ansible 2>/dev/null
fi
# 3. SSH Directory & Key Setup
mkdir -p /home/ansible/.ssh
# 4. SSH StrictModes Fix (Crucial for Alpine/Hardened templates)
# Home dir must NOT be group-writable
chown root:root /home/ansible
chmod 755 /home/ansible
# Check if key exists, otherwise append
if ! grep -q \"$PUB_KEY\" /home/ansible/.ssh/authorized_keys 2>/dev/null; then
echo \"$PUB_KEY\" >> /home/ansible/.ssh/authorized_keys
fi
# Set strict perms on the .ssh folder itself
chown -R ansible:ansible /home/ansible/.ssh
chmod 700 /home/ansible/.ssh
chmod 600 /home/ansible/.ssh/authorized_keys
# 5. Sudoers Configuration
mkdir -p /etc/sudoers.d
echo 'ansible ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/ansible
chmod 440 /etc/sudoers.d/ansible
# 6. Force SSHD to allow PubKey and use correct keys file
sed -i 's/#PubkeyAuthentication yes/PubkeyAuthentication yes/' /etc/ssh/sshd_config
sed -i 's/#AuthorizedKeysFile/AuthorizedKeysFile/' /etc/ssh/sshd_config
# 7. Restart SSH service to apply changes
if [ -f /etc/init.d/\$SSH_SERVICE ]; then
/etc/init.d/\$SSH_SERVICE restart
elif command -v systemctl >/dev/null 2>&1; then
systemctl restart \$SSH_SERVICE
fi
echo 'LXC $id Onboarding Complete!'
"
done

Set the script to executable and run it.

Terminal window
chmod +x ~/ansible_create_service_accounts_script.sh
./ansible_create_service_accounts_script.sh

Configure Privilege Escalation in Semaphore

Inside Semaphore, we need to configure how Ansible escalates privileges when connecting to our LXCs. We set up environment variables in Semaphore for LXC-Sudo using JSON, like this:

  1. In the Semaphore UI, click “Variable Groups” from the left menu.
  2. Click “New Group”.
  3. Select “JSON” from the modal box.
  4. Paste in the following JSON.
{
"ansible_become": true,
"ansible_become_method": "sudo",
"ansible_become_user": "root"
}

Set Up Inventory of Hosts in Semaphore

Once your endpoints are configured, the next step is defining your inventory: the hosts Ansible will target. Inventories are defined in YAML and group your hosts logically.

Inventories are selected based on task. You can have as many as you wish, whether you want to target specific hosts or all hosts. This will be important later when we create tasks in Semaphore and choose which machines they execute on.

  1. In Semaphore UI, click “Inventory” on the left navigation menu.
  2. Click the “New Inventory” button at the top right of the page.
  3. Fill in the boxes. I’ve included an example below.
  • Name: Homelab
  • User Credentials: Ansible Service Account
  • Sudo Credentials (Optional): Blank
  • Type: Static YAML

Here’s an example YAML where I’ve included hosts by DNS and hosts by IP as examples. Add as many subcategories as you wish, but be mindful of strict YAML spacing rules.

all:
children:
infrastructure:
hosts:
semaphore.domain.internal:
caddy.domain.internal:
external_nodes:
hosts:
web_server_01:
ansible_host: 192.168.XXX.XXX

I keep bringing up words like hosts, LXCs, endpoints, and servers interchangeably. As you can see from the inventory, it’s really just any endpoint that accepts SSH.

Semaphore Add Inventory Screen

Set Up Repositories

To set up a proper CI/CD pipeline, we should really be using some sort of git tracking and connecting Semaphore UI to the repository. In this example, I’m using GitHub, but any git-based system will work.

This is beneficial for a number of reasons:

  • All of our playbooks exist on a separate system, which acts as a sort of passive backup.
  • All the benefits of git.
  • Multiple people can maintain a single playbook base. This is less important for most homelabs, but it can make it easier to get help from a buddy when sharing a playbook with someone else.

Considerations:

  • A public repository means your playbooks can be read by others. Keep that in mind and don’t share secrets accidentally. Semaphore has a number of solutions within the interface to set variables, secrets as files, etc.

Connect GitHub to Semaphore

  1. Go ahead and create a Playbooks repository if you haven’t already.
  2. Go to “Settings” for that repository.
  3. Click “Deploy keys” on the left side of the page.
  4. Click the “Add deploy key” button.

Leave that tab open and switch back to your Semaphore Server CLI. We’ll create a new key for GitHub similar to how we created the Ansible Public/Private key pair above.

Terminal window
ssh-keygen -t ed25519 -C "semaphore-to-github" -f ~/.ssh/id_semaphore_github

Now we need to copy the new public key to our GitHub repo.

Terminal window
nano -w ~/.ssh/id_semaphore_github.pub
  1. Copy the entire key.
  2. Paste the key contents into the “Add deploy key” form on GitHub.

Now open a tab to your Semaphore UI:

  1. Choose “Key Store” on the left side menu.
  2. Click “New Key” button just like we did with the Ansible key.
  • Key Name: github-ssh-key
  • Type: SSH Key
  • Username: (leave blank)
  • Passphrase: (leave blank)
  • Private Key: Paste the contents of your clipboard from ~/.ssh/id_semaphore_github
  1. Save.

Now add the new repository:

  1. Choose Repositories from the left side menu.
  2. Click the “New Repository” button.

Fill in the fields as follows:

  • Name: homelab-ansible-playbooks
  • URL: The URL found on the git repository page. Example: https://github.com/bradgillap/ansibleplaybooks.git
  • Branch: main
  • Access Key: github-ssh-key
  1. Save.

Semaphore Add Repository

Workflow Example

  1. When we create playbooks for Ansible, we commit our changes to the GitHub repository.
  2. Executing an Ansible task in Semaphore UI pulls the latest playbooks from the repository.
  3. Ansible compares locally stored playbooks to the repository code to make sure it’s current.
  4. Ansible processes the playbook, logging and reporting any errors to the UI.
  5. Semaphore UI stores a nice audit log of past execution actions.

Testing

Alright! Yes, that was a lot to do but we should now have a system where we can write Playbooks, store them on a git repo and deploy them as tasks.

So let’s create our first test task.

Create a playbook in your repository:

---
- name: Test LXC Connectivity
hosts: all # This targets everything in your Semaphore Inventory
gather_facts: false
tasks:
- name: Try to ping the containers
ansible.builtin.ping:

In your Semaphore UI:

  1. Click “Task Templates” on the left side navigation menu.
  2. Click the “New Template” button and choose “Ansible Playbook”.
  3. Look for the “Repository” dropdown and click “Set Branch”

Fill out the following options:

  • Name: 01 - Github Ping LXCs Test
  • Path to playbook file: If the file is in the root of your repository it’s just the filename. Mine is ping-lxcs.yml
  • Inventory: Homelab or whatever you named the inventory of hosts.
  • Repository: This is the name of the git repository we added earlier.
  • Branch: Probably “main” if you used GitHub.
  • Variable Group: LXC-Sudo
  • View: All

Semaphore Add Task Box

Run the task and review the log. If everything is set up properly, it should say success once completed.

If that worked out okay, you can now create more tasks and write more Ansible playbooks. If you need some inspiration for common Debian-based tasks, I have a small repo located below.

ansibleplaybooks 🔗

Just the Beginning

Infrastructure as code is an entire world unto itself and the professional side of enterprise IT. There are many resources online and examples to draw from. Ansible on its own has many more features you should explore as you standardize your pipelines. Just like Ansible, Semaphore UI also has an expanded system of automation. This well runs very deep. I recommend spending some time on the Ansible documentation website at the very least. Try writing a few custom playbooks yourself. It may also be a good idea to set up a second inventory with a test Debian LXC for testing your scripts, to prevent unexpected accidents when taking destructive actions.

References

[1] Michielverbeek, "Gelsenkirchen-Erle, pipeline along the Emscher," *Wikimedia Commons* 2018. [Online]. Available: https://commons.wikimedia.org/wiki/File:Gelsenkirchen-Erle,_leiding_langs_de_Emscher_IMG_8418_2018-09-01_12.05.jpg Accessed: Apr. 19, 2026.

[2] R. Hat, "Ansible Documentation," *Ansible* 2026. [Online]. Available: https://docs.ansible.com/ Accessed: Apr. 19, 2026.

[3] S. Community, "Semaphore UI Documentation," *Semaphore UI* 2026. [Online]. Available: https://semaphoreui.com/docs/ Accessed: Apr. 19, 2026.

[4] P. S. S. GmbH, "Proxmox VE Documentation," *Proxmox* 2026. [Online]. Available: https://pve.proxmox.com/pve-docs/ Accessed: Apr. 19, 2026.