Ansible Installation (Debian) #
Official Documentation: https://docs.ansible.com/ansible/latest/installation_guide/installation_distros.html#installing-ansible-on-debian
# Install requirements
sudo apt install gnupg -y
# Ansible installation script for Debian 12
UBUNTU_CODENAME=jammy
wget -O- "https://keyserver.ubuntu.com/pks/lookup?fingerprint=on&op=get&search=0x6125E2A8C77F2818FB7BD15B93C4A3FD7BB9C367" | sudo gpg --dearmour -o /usr/share/keyrings/ansible-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/ansible-archive-keyring.gpg] http://ppa.launchpad.net/ansible/ansible/ubuntu $UBUNTU_CODENAME main" | sudo tee /etc/apt/sources.list.d/ansible.list
sudo apt update && sudo apt install ansible -y
# Verify installation / list version
ansible --version
Ansible Playground #
File & Folder Structure #
# The file and folder structure looks like this:
ansible-playground
├── ansible.cfg
├── inventory
├── playbooks
│ └── example_playbook.yml
└── roles
└── example_role
└── tasks
└── main.yml
# Create the folder structure
mkdir -p \
ansible-playground/playbooks \
ansible-playground/roles/example_role/tasks \
&& cd ansible-playground
Ansible Configuration #
- ansible.cfg
[defaults]
roles_path = ./roles
Inventory File #
- inventory
[example_hosts]
192.168.30.161 ansible_user=debian
Example Playbook #
- playbooks/example_playbook.yml
---
- name: Example Playbook
hosts: example_hosts
become: true
gather_facts: true
roles:
- example_role
Example Role #
-
roles/example_role/tasks/main.yml
-
Add the tasks to the
main.yml
file. -
All tasks were tested on Debian 12 server.
Task: Create User #
- This task creates a new user with home directory, adds the user to the sudo and docker group, allowes the user to uses sudo without password and adds a public SSH key to the “~/.ssh/authorized_keys” file of the user.
---
- name: Ensure user exists
ansible.builtin.user:
name: example-user
shell: /bin/bash
home: /home/example-user
groups: sudo, docker
append: yes # Add user to groups, keep user in any other groups the user might already be part of
expires: -1 # No expiration
state: present
create_home: yes
- name: Add public SSH key to authorized_keys
ansible.posix.authorized_key:
user: example-user
state: present
key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC56iSOcPyN6CGW4RAzcv7w1YxO3wJMY9mC1lEAT5W6SgnjiUjQFstQyNCJJcrPNHYF1sjMfbN8lThY/b007fwVh9XQX7tnVZ6rQ5CU1VrmgMzyMPkcLPoIaVTWSggDRu/PUtrBtcURje+HZQno9oRYyA768l6acG0PSHwftRyyKcJ4TE/4Iv6tmQPXi5HKt4aIRy1MYqtMR3FcKd+enCx0I9TM1+eSL/vxfYS+e2MZJwnz/Zl8HJxBg/XRfti7FIydy2CvPszQkIvFrMpP8BQhvNHtIWDzooabLnxuAv7//xlWwwC6GXBeQ+UxWmn6VUexpN6Hr0WUqI7ErmODhPLqzXtOPHhfq8MC2SAtZxx9+b+yjpjT0rlDnm1Fv9MWi0kEiYiYnJxsJZZiEKAxwUSb0pz/uneU1Cj66A6xoFNCwG3H6bW7gySGtKWtjrjM2EmwzRAvA7h4xZufLns0tPx4MhR9AaounWpdj1vaJhJiHAACG2scmJMjUYhMTuY9lIUUW9viak/z8VlBsKCgve+oSRZrXg84kleHR8eREeAtDwQdPEtxXgNID2B9v2XwDYVo3Lgl41tSiloA7TU/49GG6SvCsyVfW1r2AdlI/QBoncoP3OxixxfDNauVg7omJ0W8Fh1MSb7h+G5Hq8mV2l8L1s75yPTVnmOrMt3jfIjlEQ== debian@debansible01"
- name: Allow user to use sudo without a password
ansible.builtin.copy:
dest: /etc/sudoers.d/example-user
content: "example-user ALL=(ALL) NOPASSWD:ALL\n"
mode: '0440'
owner: root
group: root
Task: Delete User #
Delete the user but keep the users home directory:
---
- name: Remove user account and home directory
ansible.builtin.user:
name: example-user
state: absent
Delete the user and the users home directory:
---
- name: Remove user account and home directory
ansible.builtin.user:
name: example-user
state: absent
remove: yes # Delete home directory
Task: Create Group #
---
- name: Ensure group exists
ansible.builtin.group:
name: example-group
gid: 1010
state: present
Task: Create Directory #
---
- name: Ensure directory exists
ansible.builtin.file:
path: /opt/example-directory
state: directory
owner: debian
group: debian
recurse: yes
become: true
Task: Create File with Content #
---
- name: Ensure file exists
ansible.builtin.copy:
dest: /tmp/example-file
content: |
some text
more text
owner: debian
group: debian
Task: Create Symlink #
- name: Create symlink
ansible.builtin.file:
src: /tmp/some-script
dest: /usr/local/bin/some-script
state: link
become: true
Task: Copy Static File #
# The file and folder structure looks like this:
ansible-playground
├── ansible.cfg
├── inventory
├── playbooks
│ └── example_playbook.yml
└── roles
└── example_role
├── files
│ └── example-file.txt
└── tasks
└── main.yml
# Create the folder structure
mkdir -p roles/example_role/files
# Create static file that will be copied
vi roles/example_role/files/example-file.txt
- roles/example_role/tasks/main.yml
- name: configure gitlab docker dnd runner
copy:
src: example-file.txt
dest: /tmp/destination-file.txt
owner: debian
group: debian
mode: "600"
Task: Copy File from Template #
# The file and folder structure looks like this:
ansible-playground
├── ansible.cfg
├── inventory
├── playbooks
│ └── example_playbook.yml
└── roles
└── example_role
├── tasks
│ └── main.yml
└── templates
└── config.toml.j2 # template file
# Create the folder structure
mkdir -p roles/example_role/templates
Create the template file:
- roles/example_role/templates/config.toml.j2
concurrent = 5
check_interval = 0
shutdown_timeout = 0
[session_server]
session_timeout = 1800
[[runners]]
name = "example-runner"
url = {{ gitlab_runner_url }}
id = 1
token = {{ gitlab_runner_token }}
executor = "docker"
Define the varibales in the playbook:
- playbooks/example_playbook.yml
---
- name: Example Playbook
hosts: example_hosts
become: true
gather_facts: true
# Define the variables that are used by the template
vars:
gitlab_runner_url: "https://gitlab.example.com/"
gitlab_runner_token: "SOME-TOKEN"
roles:
- example_role
Create the task thak copies the file from the template:
- roles/example_role/tasks/main.yml
---
- name: Ensure GitLab Runner config directory exists
file:
path: /etc/gitlab-runner
state: directory
owner: root
group: root
mode: '0755'
- name: Deploy GitLab Runner config from template
template:
src: config.toml.j2
dest: /etc/gitlab-runner/config.toml
owner: root
group: root
mode: '0644'
Task: Clone Git Repository #
The following task clones a git repository from GitHub and pulls the latest version, every time the playbook triggers the task:
---
- name: Ensure git repository exists
ansible.builtin.git:
repo: https://github.com/octocat/Hello-World.git
dest: /tmp/github-repository # The content of the git repo gets cloned into this directory
version: master
update: yes
clone: yes
force: yes
become_user: debian
Task: Install Packages #
---
- name: Ensure packages are installed
ansible.builtin.apt:
name: "{{ item }}"
state: latest
update_cache: true
cache_valid_time: 0
loop:
- sudo
- git
- curl
- neovim
Task: Uninstall Package #
- The task is idempotent, if the package is already uninstalled, nothing happens.
Uninstall a package:
---
- name: Uninstall package
ansible.builtin.apt:
name: ufw
state: absent
purge: yes # Remove the package and its configuration files
Uninstall several packages:
- name: Uninstall packages
ansible.builtin.apt:
name: "{{ item }}"
state: absent
purge: yes # Remove the package and its configuration files
loop:
- curl
- ufw
- git
Task: Install & Configure UFW Firewall #
-
Using
state: reset
clears all existing firewall rules before applying new ones. This ensures that the firewall configuration matches exactly what is defined in the playbook. -
to remove a port, simply delete it from the list and rerun the playbook.
- name: Ensure UFW firewall is installed
ansible.builtin.apt:
name: ufw
state: latest # If already installed, update to the latest version
update_cache: yes # Update the APT cache
- name: Reset UFW firewall rules
community.general.ufw:
state: reset
- name: Add UFW firewall rules
community.general.ufw:
rule: allow
port: "{{ item }}"
proto: tcp
loop:
- 22
- 80
- 443
- name: Ensure UFW is enabled
community.general.ufw:
state: enabled
policy: deny # Everything is blocked by default
Task: Install Docker & Docker Compose #
---
# Install required dependencies for Docker
- name: Install dependencies
ansible.builtin.apt:
name: "{{ item }}"
state: latest
update_cache: true
cache_valid_time: 0
loop:
- ca-certificates
- curl
- git
- gnupg
- lsb-release
# Install Docker & Docker Compose
- name: Ensure /etc/apt/keyrings directory exists
ansible.builtin.file:
path: /etc/apt/keyrings
state: directory
mode: '0755'
- name: Add Docker GPG key
ansible.builtin.get_url:
url: https://download.docker.com/linux/debian/gpg
dest: /etc/apt/keyrings/docker.asc
mode: '0644'
- name: Add Docker APT repository
ansible.builtin.apt_repository:
repo: "deb [arch={{ (ansible_architecture == 'aarch64') | ternary('arm64', (ansible_architecture == 'x86_64') | ternary('amd64', ansible_architecture)) }} signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian {{ ansible_distribution_release }} stable"
state: present
filename: docker
- name: Install docker
ansible.builtin.apt:
name: "{{ item }}"
state: present
update_cache: true
cache_valid_time: 0
loop:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-buildx-plugin
- docker-compose-plugin
# Add user to Docker group
- name: Ensure user is in the Docker group
ansible.builtin.user:
name: debian
groups: docker
append: yes
# Reboot
- name: Reboot if needed (optional)
ansible.builtin.reboot:
msg: "Rebooting to apply docker group membership"
reboot_timeout: 120
when: reboot_required | default(true)
Task: Start Docker Container #
---
- name: Start Docker container
docker_container:
name: nginx
image: "nginx:latest"
restart_policy: unless-stopped
ports:
- "8080:80"
Task: Start Docker Container with Volume Mapping #
---
- name: Ensure directory exists
ansible.builtin.file:
path: /opt/nginx/data
state: directory
mode: '0755'
owner: debian
group: debian
recurse: yes
become: true
- name: Ensure file exists
ansible.builtin.copy:
dest: /opt/nginx/data/index.html
content: |
hi there
some text
owner: debian
group: debian
- name: Start a Docker container with volume mapping
docker_container:
name: nginx
image: "nginx:latest"
restart_policy: unless-stopped
ports:
- "8080:80"
volumes:
- "/opt/nginx/data:/usr/share/nginx/html"
Task: Stop Docker Container #
---
- name: Stop Docker container without removing it
docker_container:
name: nginx
state: stopped
Task: Stop & Remove Docker Container #
---
- name: Stop and remove Docker container
docker_container:
name: nginx
state: absent
Task: Start Docker Compose Stack #
-
This task creates a “docker-compose.yml” file with the specified content. As an alternative, the file could be copied from a static file or template.
-
The stack is started using a command, which is not idempotent. The task runs even if the stack is already up, and Ansible will always report a “changed” status.
---
- name: Ensure directory exists
ansible.builtin.file:
path: /opt/nginx-stack
state: directory
owner: debian
group: debian
recurse: yes
become: true
- name: Ensure file exists
ansible.builtin.copy:
dest: /opt/nginx-stack/docker-compose.yml
content: |
services:
nginx:
image: nginx:latest
restart: unless-stopped
ports:
- "8080:80"
owner: debian
group: debian
become: true
- name: Start Docker Compose stack
ansible.builtin.command: docker compose up -d
args:
chdir: /opt/nginx-stack
become: true
become_user: debian
Task: Stop Docker Compose Stack #
---
- name: Stop Docker Compose stack
ansible.builtin.command: docker compose down
args:
chdir: /opt/nginx-stack
become: true
Task: Check Compose Status & Stop Stack #
- name: Check if Docker Compose steck is up
ansible.builtin.command: docker compose ps
register: compose_status
failed_when: false
changed_when: false
args:
chdir: /opt/nginx-stack
become: yes
- name: Stop Docker Compose stack
ansible.builtin.command: docker compose down --remove-orphans
args:
chdir: /opt/nginx-stack
when: "'Up' in compose_status.stdout"
become: yes
Run the Playbook #
# CD into Ansible project
cd ~/ansible-playground
# Run the playbook
ansible-playbook playbooks/example_playbook.yml -i inventory