Despliegue de Ansible y Semaphore en Docker para automatizar tareas Windows y Linux. Creamos una máquina virtual Linux Ubuntu sobre Proxmox, instalamos Docker y Docker Compose. Instalamos Ansible y desplegamos contenedor con Semaphore. Preparamos los servidores Linux y programamos y ejecutamos una primera tarea para revisar el correcto funcionamiento de Ansible.
- Paso 1: Crear la máquina virtual en Proxmox.
- Paso 2: Instalar Docker y Docker Compose.
- Paso 3: Desplegar Ansible Semaphore.
- Paso 4: Claves de acceso a servidores Linux.
- Primera tarea de automatización en Ansible para verificar funcionamiento y conexión con servidores.
Paso 1: Crear la máquina virtual en Proxmox
En primer lugar, crearemos la máquina virtual con Linux Ubuntu Server 26 específica para este propósito, para ello, subiremos (si aún no lo tenemos) el ISO de Ubutu Server al local del nodo Proxmox y crearemos la máquina virtual, con los siguientes datos:
- General: asignaremos un ID y un nombre, por
ansible. - OS: seleccionamos la ISO de Ubuntu 26.04 subida a local.
- System: dejamos las opciones por defecto, podemos activar Qemu Agent si queremos instalarlo posteriormente.
- Disks: asignaremos entre 30 GB y 50 GB. Si el almacenamiento en Proxmox es SSD, marcaremos la casilla Discard para optimizar el espacio.
- CPU: asignaremos 2 cores (es más que suficiente para empezar).
- Memory: asignaremos 2 GB o 4 GB de RAM.
- Network: configuraremos la red para que la VM tenga salida a Internet (para instalar los paquetes) y visibilidad hacia las redes de los servidores Linux y Windows a los que necesitemos acceder para automatizar tareas.

Iniciaremos la máquina virtual Ubuntu Server y seguiremos los pasos de instalación normales de Ubuntu Server 26:

Paso 2: Instalar Docker y Docker Compose
Una vez terminada la instalación de Linux Ubuntu Server, accederemos al nuevo servidor por SSH. Para instalar Docker y Docker Compose, ejecutaremos los siguientes comandos.
1. Actualizaremos el sistema e instalaremos los certificados básicos:
|
1 2 |
sudo apt update && sudo apt upgrade -y sudo apt install -y ca-certificates curl |

2. Agregaremos la clave GPG oficial de Docker:
|
1 2 3 |
sudo install -m 0755 -d /etc/apt/keyrings sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc sudo chmod a+r /etc/apt/keyrings/docker.asc |
3. Añadiremos el repositorio oficial de Docker para Ubuntu 26:
|
1 2 3 4 |
echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null |

4. Instalaremos el motor de Docker y Docker Compose:
|
1 2 |
sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin |

5. Habilitaremos docker para que se inicie en el arranque del equipo:
|
1 |
sudo systemctl enable --now docker |

Paso 3: Desplegar Ansible Semaphore
Ahora crearemos el contenedor donde desplegaremos la interfaz web del sistema Ansible Semaphore. Utilizaremos una base de datos PostgreSQL para almacenar los registros de las ejecuciones, usuarios e inventarios.
Crearemos una carpeta para organizar el despliegue:
|
1 |
mkdir ~/semaphore && cd ~/semaphore |
Crearemos el archivo de configuración:
|
1 |
nano docker-compose.yml |
Pegaremos el siguiente contenido dentro de docker-compose.yml (modificaremos las contraseñas de ejemplo por unas seguras):
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
services: postgres: image: postgres:14-alpine volumes: - semaphore-db:/var/lib/postgresql/data environment: POSTGRES_USER: semaphore POSTGRES_PASSWORD: Contraseña_Segura_BD # <--- Cambia esto POSTGRES_DB: semaphore restart: unless-stopped semaphore: image: semaphoreui/semaphore:latest ports: - "3000:3000" environment: SEMAPHORE_DB_USER: semaphore SEMAPHORE_DB_PASS: Contraseña_Segura_BD # <--- Debe ser la misma de arriba SEMAPHORE_DB_HOST: postgres SEMAPHORE_DB_PORT: 5432 SEMAPHORE_DB_DIALECT: postgres SEMAPHORE_DB: semaphore # Configuración del usuario Administrador Inicial: SEMAPHORE_ADMIN_PASSWORD: Contraseña_Web # <--- Cambia esto para tu login web SEMAPHORE_ADMIN_NAME: Administrator SEMAPHORE_ADMIN_EMAIL: admin@proyectoa.com SEMAPHORE_ADMIN: admin depends_on: - postgres restart: unless-stopped volumes: semaphore-db: |
Guarda el archivo pulsando Ctrl+O y cierra la edición pulsando Ctrl+X.

Iniciaremos la infraestructura docker con los contenedores indicados en el fichero docker-compose.yml en segundo plano con:
|
1 |
sudo docker compose up -d |

Los contenedores, imágenes y volúmenes se crearán automáticamente y, por defecto, se levantará el puerto 3000:

Ya podremos abrir un navegador en cualquier equipo de la misma red del servidor Ansible y navegar a la URL:
http://IP_Servidor_Ansibe:3000
Nos mostrará la ventana de login de Semaphore:

En cuanto accedamos por primera vez a Semaphore, nos mostrará la ventana para crear un proyecto nuevo, en Nombre del Proyecto introduciremos «Automatización» y pulsaremos en «Crear»:

Paso 4: Claves de acceso a servidores Linux
El siguiente paso natural es crear las claves de acceso a los servidores que queramos administrar. Para el caso de los servidores Linux, usaremos SSH. Para establecer las claves SSH de acceso desde el servidor Ansible Semaphore a los servidores Linux, seguiremos estos pasos.
Paso 4.1: Generar el par de claves en servidor Ansible
Accederemos por SSH al servidor Ansible Semaphore y ejecutaremos este comando:
|
1 |
ssh-keygen -t ed25519 -C "ansible-semaphore" |
Preguntará dónde guardarla (pulsaremos Enter para dejar la ruta por defecto) y pedirá una «passphrase» (contraseña). Para automatizar con Ansible de forma sencilla, dejaremos en blanco presionando Enter dos veces.

Paso 4.2: Poner la clave privada en Semaphore
Ahora vamos a obtener la clave privada por pantalla para copiarla, con:
|
1 |
cat ~/.ssh/id_ed25519 |

Por supuesto, en este proceso, es IMPRESCINDIBLE mantener la clave privada en una carpeta de acceso seguro en el servidor Linux Ansible y no exponerla en ningún momento. Si se copia en un equipo Windows, tras finalizar el proceso, conviene reiniciar el equipo para que se «libere» de memoria.
Copiamos el bloque de la clave privada, incluyendo (muy importante) las líneas —–BEGIN OPENSSH PRIVATE KEY—– y —–END OPENSSH PRIVATE KEY—–. Volvemos a la consola web de Semaphore, a nuestro proyecto creado, pulsamos en «Almacén de Claves» y en «Nueva clave»:

Introducimos un nombre para la clave, para identificarla para su posterior uso, por ejemplo «admin_linux», seleccionamos «LOCAL» y pegamos en Clave Privada la clave copiada anteriormente. En caso de haber indicado una passphrase, la introduciremos aquí. De momento, el nombre de usuario podemos indicar el que crearemos posteriormente específico en cada servidor «anb_sru»:

Paso 4.3: Repartir la clave pública en los servidores Linux que queramos administrar
Ahora indicaremos a los servidores Linux que confíen en esa llave. Para ello, deberemos tener inventariados todos los equipos Linux y conocer su usuario administrador y su dirección IP.
Para la «repartir» la clave pública tenemos varias opciones: enviarla por SSH desde el servidor de Ansible a cada servidor, en este caso, tendríamos que utilizar el usuario administrador de cada equipo y esta NO es una buena práctica, dado que para ejecutar tareas automatizadas que requieran de elevación de privilegios, a este usuario administrador de cada servidor habría que indicarle que puede ejecutar sudo… sin solicitar contraseña. Por lo tanto, esta opción NO LA RECOMENDAMOS. La otra opción, la recomendada, es crear un usuario específico para uso de Ansible en cada servidor, sin contraseña, sin posibilidad de que pueda realizar login, solo para uso de Ansible, para conectar por SSH a los equipos y poder ejecutar comandos con elevación de privilegios. Explicamos ambas opciones más adelante.
Preparar equipos servidores para conexión SSH desde servidor Ansible
Para poder realizar la gestión desde el servidor Ansible, es imprescindible que los servidores tengan el servicio SSH activo y, si tienen el cortafuegos local activado, nos debemos asegurar de que tienen acceso por SSH desde la IP del servidor de Ansible. Para cortafuegos local ufw podremos permitir acceso SSH (puerto 22) a la IP del Ansible con el comando:
|
1 |
sudo ufw allow from 192.168.10.111 to any port 22 comment 'Acceso Ansible' |

Ansible usa Python, por lo que también es conveniente que esté instalado en cada servidor y, además, conocer su ubicación, para indicársela posteriormente en el inventario de Semaphore.
Opción 1: transferir clave por SSH con usuario administrador de los servidores (NO RECOMENDADA)
Cuando tengamos la lista de servidores y estemos seguros de que tienen SSH, volveremos a la consola SSH del servidor Ansible, e iremos ejecutando el siguiente comando (hay que repetirlo por cada servidor a gestionar). Sólo necesitaremos ejecutarlo una vez, para transferir una copia de la clave pública del servidor Ansible al resto de equipos Linux:
|
1 |
ssh-copy-id alonso@192.168.20.135 |
Cambiaremos la IP y el usuario por la IP de cada servidor y el nombre del usuario administrador (con permisos de acceso SSH).
Nos solicitará confirmación, escribiremos «yes» y nos solicitará la contraseña del usuario indicado en el comando del servidor indicado en la IP.

Si los datos son correctos, transferirá la clave pública al servidor Linux indicado y, a partir de este momento, el servidor de Ansible Semaphore tendrá acceso administrativo a dicho servidor. Repetiremos el proceso para todos los servidores a administrar.
Pero, para esta opción NO RECOMENDADA, si necesitamos ejecutar tareas automatizadas en los servidores que requiera de elevación de privilegios, no funcionarán, porque el usuario administrador de los equipos, habitualmente, cuando usamos sudo … nos solicitará contraseña. En este caso, para que funcionase, habría que añadir, en el fichero sudoers, una línea, para ello (repetimos que NO es lo recomendable), en cada servidor, con el usuario administrador, ejecutaremos:
|
1 |
sudo visudo |
Y añadiremos, al final, esta línea:
|
1 |
alonso ALL=(ALL) NOPASSWD: ALL |

Con esta configuración, y por eso NO es recomendable, el usuario administrador del equipo podrá ejecutar elevación de privilegios sin que se le solicite contraseña (solo se le pedirá contraseña al iniciar sesión).
Opción 2: crear usuario seguro sin contraseña/login en cada servidor específico para Ansible (RECOMENDADO)
Es muy recomendable crear un usuario específico en los servidores para uso de Ansible. Este usuario será el que se emplee para las automatizaciones que requieran de elevación de privilegios (sudo…).
Para evitar que los comandos sudo… fallen, crearemos un usuario administrador en cada servidor, sin contraseña, pues usará la clave SSH pública de Ansible. Accederemos a cada servidor y crearemos un usuario, por ejemplo «anb_sru», sin contraseña (no tendrá acceso con login normal):
|
1 |
sudo adduser --disabled-password --gecos "" anb_sru |
Creamos un archivo dedicado dentro de la carpeta sudoers.d. Esto es mucho más seguro que editar el archivo principal /etc/sudoers. Le indicamos que el usuario creado anteriormente puede ejecutar cualquier comando como administrador sin que se le pida contraseña:
|
1 |
echo "anb_sru ALL=(ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/anb_sru |
Accedemos con el usuario creado para asignarle su estructura de carpetas de seguridad. Así nos aseguramos de que él sea el propietario legítimo de los archivos:
|
1 2 3 4 5 |
# Cambiamos al usuario creado sudo su - anb_sru # Creamos la carpeta oculta de SSH y le damos permisos estrictos (solo el dueño puede leer/escribir) mkdir ~/.ssh chmod 700 ~/.ssh |
Ahora necesitamos conocer la clave pública del servidor Ansible, así que volvemos a la consola shell del servidor Ansible y ejecutamos el siguiente comando, que nos devolverá la clave pública (la copiamos):

Volvemos al servidor, y creamos el archivo de claves y pegamos dentro la clave pública que hemos generado en nuestro servidor Semaphore/Ansible:
|
1 |
nano ~/.ssh/authorized_keys |
Pegaremos la clave generada aneriormente en el servidor Ansible y guardamos.
Damos los permisos obligatorios de Linux para este archivo (si no tiene los permisos 600, el servidor SSH rechazará la conexión por considerarlo inseguro):
|
1 |
chmod 600 ~/.ssh/authorized_keys |
Primera tarea de automatización en Ansible para verificar funcionamiento y conexión con servidores
Aunque la práctica recomendada es usar Git (en local o remoto) para gestionar los playbooks de tareas, para tener control de versiones y modificaciones, Ansible también permite playbooks locales. Para realizar una primera automatización de prueba, vamos a usar los playbooks locales.
Accederemos al servidor de Ansible por SSH y crearemos una carpeta para almacenar los playbooks (usaremos la carpeta creada en el proceso de despliegue del sistema «~/semaphore»):
|
1 |
mkdir -p ~/semaphore/playbooks |
Crearemos nuestro primer playbook «inventario.yml»:
|
1 |
nano ~/semaphore/playbooks/inventario.yml |
Pegaremos el siguiente YAML que obtiene el tiempo que está el servidor encendido, el espacio en la unidad raíz y el nombre del equipo:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
--- - name: Inventario de equipos Linux hosts: all gather_facts: no tasks: - name: Obtener iempo encendido (Uptime) command: uptime register: uptime_result - name: Obtener espacio en disco de la raiz command: df -h / register: disk_result - name: Mostrar informacion recolectada debug: msg: - "Servidor: {{ inventory_hostname }}" - "Uptime: {{ uptime_result.stdout }}" - "Espacio en disco:" - "{{ disk_result.stdout_lines }}" |

IMPORTANTE: dado que Semaphore está corriendo en docker, deberemos establecer esta carpeta del equipo Ubuntu Server local como volumen en el contenedor Docker. Para ello, editaremos el archivo docker-compose.yml que creamos anteriormente para el despliegue:
|
1 |
sudo nano ~/semaphore/docker-compose.yml |
en «semaphore», antes de «depends_on», añadimos (manteniendo la indentación):
|
1 2 |
volumes: - ./playbooks:/playbooks |

Es importante limitar los permisos para la carpeta playbooks, dado que podríamos guardar scripts con contraseñas en plano. Además, esta carpeta debe ser accesible por Docker, para que sea accesible, a su vez, por el contenedor con Ansible/Semaphore:
|
1 2 3 |
sudo chown -R $USER:1001 ~/semaphore/playbooks find ~/semaphore/playbooks -type d -exec chmod 750 {} \; find ~/semaphore/playbooks -type f -exec chmod 640 {} \; |
Qué significan los permisos aplicados anteriormente:
- Propietario (
$USER/ admin): tiene permisos7(en carpetas) y6(en archivos). Esto significa que puede leer, escribir, modificar y borrar cualquier playbook desde el shell de comandos. - Grupo (
1001/ Docker Semaphore): tiene permisos5(en carpetas) y4(en archivos). Esto le da permiso de Solo Lectura. El contenedor puede leer los scripts de Python y tus Playbooks de Ansible para ejecutarlos, pero no puede modificarlos ni borrarlos. - Otros (
0): el último cero es el escudo. Cualquier otro usuario que acceda a al servidor Ubuntu (otros administradores, servicios web, o un intruso) se encontrará con «Permiso Denegado» si intenta hacer un simplecata un script para visualizarlo.
Para aplicar los cambios, desde la carpeta donde tenemos el fichero docker-compose.yml, volveremos a ejecutar el comando:
|
1 |
sudo docker compose up -d |
Volveremos a la consola web de gestión de Semaphore, pulsaremos en «Repositorios» y en «Nuevo repositorio»:

Introduciremos los siguientes datos:
- Nombre: nombre identificativo para el repositorio, por ejemplo «Local»
- URL o ruta: indicaremos la ruta exacta donde hemos guardado el fichero inventario.yml. Dado que Semaphore se ejecuta en un contenedor Docker y dado que hemos redireccionado la carpeta real /semaphore/playbooks a /playbooks en el contenedor, en este campo introduciremos: /playbooks
- Clave de Acceso: seleccionaremos la clave creada anteriormente «admin_linux».

Crearemos un inventario con las máquinas Linux que queramos administrar, para ello, pulsaremos en «Inventario» y en «Nuevo inventario» – «Ansible Inventory»:

Introduciremos los siguientes datos:
- Nombre: nombre descriptivo del inventario, por ejemplo «Servidores_Linux».
- Credenciales de Usuario: elegiremos la clave creada anteriormente «admin_linux».
- Tipo: elegiremos Static.
- Y en el recuadro, en formato INI, introduciremos todos los servidores (nombre e IP), por ejemplo:
|
1 2 3 4 5 6 |
[servidores_linux] mariadb1 ansible_host=192.168.20.140 webproxy ansible_host=192.168.20.135 webnodo1 ansible_host=192.168.20.136 webnodo2 ansible_host=192.168.20.137 webnodo3 ansible_host=192.168.20.138 |
Y, muy importante, añadiremos también al recuadro anterior el usuario que hemos usado para la conexión SSH con cada servidor:
|
1 2 |
[servidores_linux:vars] ansible_user=usuario_ssh |
Y si el usuario es diferente para cada servidor, introduciremos los datos así:
|
1 2 3 4 5 6 |
[servidores_linux] mariadb1 ansible_host=192.168.20.140 ansible_user=usuario_maria webproxy ansible_host=192.168.20.135 ansible_user=usuario_proxy webnodo1 ansible_host=192.168.20.136 ansible_user=usuario_nodo1 webnodo2 ansible_host=192.168.20.137 ansible_user=usuario_nodo2 webnodo3 ansible_host=192.168.20.138 ansible_user=usuario_nodo3 |
Ansible permite, incluso, hacer un híbrido, es decir, si por ejemplo varios de los servidores usan el mismo usuario, por ejemplo «usuario_ssh», y algunos un usuario concreto, podremos indicar:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
[servidores_linux] # Ejemplo de servidores con usuario específico webproxy ansible_host=192.168.20.135 ansible_user=alonso webproxy ansible_host=192.168.20.136 ansible_user=juan # El resto usuarán el usuario que indiquemos más abajo mariadb1 ansible_host=192.168.20.140 webnodo2 ansible_host=192.168.20.137 webnodo3 ansible_host=192.168.20.138 [servidores_linux:vars] ansible_user=usuario_para_el_resto |
Y un detalle más, Ansible necesita Python en los servidores, si no le indicamos en qué ruta está, lo buscará automáticamente pero mostrará un warning. Para evitar el warning, indicaremos en qué ruta está Python en el recuadro anterior:
|
1 2 |
[servidores_linux:vars] ansible_python_interpreter=/usr/bin/python3 |
Y si en algún servidor está en otra ruta:
|
1 2 3 4 |
[servidores_linux] mariadb1 ansible_host=192.168.20.140 # A este servidor le decimos exactamente dónde está Python webproxy ansible_host=192.168.20.135 ansible_user=alonso ansible_python_interpreter=/usr/local/bin/python3.11 |

Para esta primera automatización no necesitamos variables complejas, pero crearemos una vacía para uso futuro. Pulsaremos en Grupos de Variables y en «Nuevo grupo»:

Introduciremos un nombre y dejaremos todo lo demás vacío:

Por último, para crear la tarea propiamente dicha, pulsaremos en «Plantillas de Tareas» y en «Nueva plantilla»:

Elegiremos «Ansible Playbook»:

Introduciremos los siguientes datos para crear la tarea:
- Nombre: Inventario_Linux.
- Ruta al archivo de playbook: inventario.yml.
- Inventario: Servidores_Linux.
- Repositorio: Local.
- Grupos de Variables: Variables_Linux.
Dejaremos todo lo demás por defecto y pulsaremos en «Crear»:

Una vez creada la tarea, para ejecutarla, pulsaremos en el botón «Play»:

En la ventana que nos muestra, pulsaremos en «Ejecutar»:

Si al ejecutar la tarea muestra este error:
12:30:22 PM Failed to install inventory: cannot decrypt access key, perhaps encryption key was changed
12:30:22 PM Failed to run task: cannot decrypt access key, perhaps encryption key was changed
Se debe a que hemos establecido la clave antes de recrear el contenedor (tras aplicar el cambio de mapeo a carpeta /playbooks) y, como no hemos persistido la carpeta de configuración del contenedor, cada vez que lo reiniciemos cambiará la key, por lo que habría que volver a crear la clave. Para evitar esto, vamos a persistir la configuración, editando el fichero docker-compose.yml y añadiendo:
|
1 |
- ./config:/etc/semaphore |
Justos debajo de la línea «- ./playbooks:/playbooks»:

Crearemos la carpeta «config» dentro de ~/semaphore:
|
1 |
mkdir -p ~/semaphore/config |
Y le estableceremos los permisos:
|
1 |
sudo chmod 777 ~/semaphore/config |
Volveremos a recrear el contenedor para que se aplique el cambio:
|
1 |
sudo docker compose up -d |
Y deberemos volver a crear la clave:

Una vez corregidos los problemas, si ejecutamos la tarea, funcionará perfectamente:
