93 hands-on labs to practise for the Red Hat Certified Engineer (EX294), roughly 68 hours of work, each one graded on the state of your machine rather than on the commands you typed. They run on your own hardware with the `dsoxlab` command line, and cost nothing.
Automation with Ansible, on Red Hat Enterprise Linux machines. The RHCSA is its prerequisite, and the exam stays entirely hands-on.
- 93labs
- 68hours of practice
- 86on real VMs
Levels 30 beginner 43 intermediate 20 advanced
Official exam objectives published by Red Hat
These are not exam questions. They are hands-on exercises on the skills the exam measures, graded on the state of your machine.
Ansible and the RHCE EX294
Add this catalogue dsoxlab catalog add https://github.com/stephrobert/ansible-training
Preparing the environment
Discovering Ansible
-
Declarative vs imperative: why Ansible does not drift
Compare a Bash script that drifts on every run with a playbook that converges, and prove idempotence with changed=0 on the second pass.
dsoxlab start decouvrir-declaratif-vs-imperatif -
Ansible installation: verify your control node
Identify your install method, check the 8 binaries on PATH, the modules reachable via ansible-doc and the collections the repo relies on.
dsoxlab start decouvrir-installation-ansible -
ansible.cfg: precedence and critical options
Write a project ansible.cfg, check the active config with ansible-config dump, override an option by environment variable and enable a callback.
dsoxlab start decouvrir-configuration-ansible -
Ansible CLI: the 8 everyday commands
Chain ad-hoc runs, ansible-doc, ansible-config, ansible-inventory, ansible-galaxy, ansible-vault and ansible-lint across the lab.
dsoxlab start decouvrir-prise-en-main-cli
First steps
-
First playbook: install nginx on the webservers
Write a 5-task playbook (dnf, systemd, firewalld), read the PLAY RECAP, capture output with register and check idempotence on the second pass.
dsoxlab start premiers-pas-premier-playbook -
First steps with ansible-vault
Encrypt a secrets file with ansible-vault, consume it through vars_files and protect output with no_log and a 0600 .vault_password.
dsoxlab start premiers-pas-ansible-vault
Writing Ansible code
-
Plays and tasks: anatomy and execution order
Structure a play with pre_tasks, tasks, post_tasks and handlers, then prove the real execution order with timestamped marker files.
dsoxlab start ecrire-code-plays-et-tasks -
handlers: the restart-on-config-change pattern
Notify several handlers, decouple them with listen, force meta: flush_handlers and pair validate so an invalid config is never applied.
dsoxlab start ecrire-code-handlers -
tags: target or skip a subset of tasks
Set tags, target with --tags and exclude with --skip-tags, inspect the plan via --list-tasks and use the special always and never tags.
dsoxlab start ecrire-code-tags -
check mode and diff: dry-run and change preview
Run a playbook with --check --diff, spot the modules that do not support check, force check_mode: false and diagnose a false changed positive.
dsoxlab start ecrire-code-checkmode-diff -
Variables: declaration and scope
Declare variables with vars and vars_files, override them with --extra-vars and diagnose an unexpected value caused by YAML typing.
dsoxlab start ecrire-code-variables-base -
Collection types: lists, dicts, nested structures
Declare lists and lists of dicts in YAML, loop over them with loop_control: label, filter with when and reach nested fields.
dsoxlab start ecrire-code-types-collections -
facts and magic vars: ansible_facts, hostvars
Read system facts, use inventory_hostname, groups and hostvars, then cut the collection cost with gather_subset.
dsoxlab start ecrire-code-facts-magic-vars -
Custom facts: facts.d and ansible_local
Drop an INI custom fact then an executable script returning JSON into /etc/ansible/facts.d, and read them back through ansible_local.
dsoxlab start ecrire-code-custom-facts -
Variable precedence: the 22 levels
Stack the same variable at several levels to demonstrate which one wins, from role defaults up to --extra-vars.
dsoxlab start ecrire-code-precedence-variables -
register and set_fact: capture and create variables
Capture module output with register, reuse it in when and loop, create a runtime fact and persist it with cacheable: true.
dsoxlab start ecrire-code-register-set-fact -
Parallelism: forks, serial, throttle, strategy
Tell forks from serial, run a rolling update with serial: 1, compare the linear and free strategies and rate-limit a single task with throttle.
dsoxlab start ecrire-code-parallelisme-strategies -
async and poll: long tasks without blocking SSH
Detach a long task with async + poll: 0, collect its result via async_status, poll actively with until and diagnose an orphaned job.
dsoxlab start ecrire-code-async-poll -
Delegation: delegate_to, run_once, local_action
Redirect a task to another host, run it only once in a multi-host play and target the control node with local_action.
dsoxlab start ecrire-code-delegation -
lookups: fetch external data at runtime
Read a file, an environment variable or a command output on the control node, generate a password and tell lookup from query.
dsoxlab start ecrire-code-lookups -
Jinja2: interpolation, logic and whitespace
Interpolate variables, loop and branch inside a template, then kill stray blank lines with whitespace control.
dsoxlab start ecrire-code-jinja2-base -
Essential Jinja2 filters: default, combine, selectattr
Handle missing variables with default, work on lists, filter a list of dicts with selectattr and map, merge dicts with combine.
dsoxlab start ecrire-code-filtres-jinja-essentiels -
when conditions: operators and Jinja tests
Gate a task on a fact, combine several conditions, test whether a variable is defined and diagnose a when that matches wrongly.
dsoxlab start ecrire-code-conditions-when -
loop: iterate over lists and dicts
Loop over a list and a list of dicts, keep console output readable with loop_control: label, iterate a dict with dict2items.
dsoxlab start ecrire-code-boucles-loop -
Legacy with_* loops: migrating to loop
Recognise with_items, with_dict and with_subelements, migrate them to loop plus Jinja2 filters and automate it with ansible-lint --fix.
dsoxlab start ecrire-code-boucles-with-deprecated -
block, rescue, always: try/catch/finally
Group tasks in a block, catch the error with rescue to roll back, guarantee cleanup with always and read ansible_failed_task.
dsoxlab start ecrire-code-block-rescue-always -
failed_when and changed_when: redefine success and change
Neutralise changed on read-only commands, define changed_when from output and accept some return codes as success.
dsoxlab start ecrire-code-failed-when-changed-when -
ignore_errors: legitimate use vs anti-pattern
Measure the effect of ignore_errors on the PLAY RECAP, spot its few legitimate cases and prefer failed_when or block/rescue.
dsoxlab start ecrire-code-ignore-errors -
any_errors_fatal: stop the play on the first error
Enable any_errors_fatal on a multi-host play, compare it with the default and with max_fail_percentage, and combine it with serial.
dsoxlab start ecrire-code-any-errors-fatal -
Advanced Jinja2 filters: regex, b64, password_hash
Extract with regex_search, encode to base64, hash a password with sha512, query JSON with json_query and handle IPs and CIDRs.
dsoxlab start ecrire-code-filtres-jinja-avances -
Jinja2 tests: is defined, is mapping, is sequence
Test a variable's definition and type, match a regex with is match and is search, and combine those tests in when and in {% if %}.
dsoxlab start ecrire-code-tests-jinja -
template module: validate, backup, whitespace
Render a config from a Jinja2 template, reject invalid syntax with validate, back up the previous file and set mode, owner and group.
dsoxlab start ecrire-code-module-template -
lineinfile vs template: which module when
Arbitrate between lineinfile, blockinfile and template, combine base plus overrides and diagnose a lineinfile that stacks for lack of a regexp.
dsoxlab start ecrire-code-lineinfile-vs-template -
import vs include: static or dynamic
Choose between import_* parsed at start and include_* resolved at runtime, and observe how tags and when behave differently in each case.
dsoxlab start ecrire-code-import-include
File modules
-
copy module: transfer files and inline content
Transfer a file with src: or write inline content with content:, mastering mode, owner, backup and validate.
dsoxlab start modules-fichiers-copy -
file module: states, permissions and symlinks
Manage the state of a file or directory (directory, absent, link, hard, touch), recursive permissions and symbolic links.
dsoxlab start modules-fichiers-file -
blockinfile module: idempotent multi-line block
Insert and maintain a multi-line block in an existing file with custom markers, insertafter/insertbefore and idempotence.
dsoxlab start modules-fichiers-blockinfile -
lineinfile module: edit a line in an existing file
Add, replace by regexp or remove a line in a config file, using backrefs and syntax validation before the write.
dsoxlab start modules-fichiers-lineinfile -
replace module: substitute a pattern in a file
Substitute a regex pattern throughout a file, scope it with before/after, and preserve parts of it with capture groups.
dsoxlab start modules-fichiers-replace -
fetch module: retrieve files from managed nodes
Collect logs and configs from managed nodes to the control node, per-host tree or flat mode with inventory_hostname.
dsoxlab start modules-fichiers-fetch -
archive and unarchive modules: compress and extract
Create a tar.gz archive on the managed node and extract local, remote or already-present tarballs idempotently with creates.
dsoxlab start modules-fichiers-archive-unarchive
Package modules
-
package module: distro-agnostic installation
Install and remove packages without depending on the distro package manager, weighing state: present against state: latest.
dsoxlab start modules-paquets-package -
dnf module: enablerepo, security, exclude, autoremove
Enable a repo on the fly, patch CVEs only, exclude the kernel from a bulk upgrade and clean up orphaned dependencies.
dsoxlab start modules-paquets-dnf-options -
yum_repository module: declare an RPM repository
Declare a yum/dnf repository with gpgcheck, import its GPG key via rpm_key, and disable a repo without removing it.
dsoxlab start modules-paquets-yum-repository
Service modules
-
systemd_service module: manage systemd services
Start, enable, reload and mask services, drop a custom unit file with daemon_reload, and notify a service from a handler.
dsoxlab start modules-services-systemd -
cron module: schedule idempotent jobs
Schedule idempotent cron jobs via a user crontab or /etc/cron.d, with environment variables and clean disabling.
dsoxlab start modules-services-cron
User modules
-
user module: create, modify and remove accounts
Create accounts with home, shell and secondary groups, hash a password, force a specific UID, and remove an account with its home.
dsoxlab start modules-utilisateurs-user -
group module: manage Linux groups
Create groups with a forced GID, tell system groups from user groups, and order group creation before the users that reference it.
dsoxlab start modules-utilisateurs-group -
authorized_key module: user SSH keys
Provision public SSH keys, force an exclusive list, restrict a key with key_options, and handle several users with subelements.
dsoxlab start modules-utilisateurs-authorized-key -
sudoers module: manage sudo rights safely
Create rules under /etc/sudoers.d/ with automatic visudo validation, limit allowed commands, and handle nopassword on a group.
dsoxlab start modules-utilisateurs-sudoers
RHEL modules
-
firewalld module: manage the RHEL firewall
Allow predefined services and custom ports per zone, with the permanent + immediate trap and the firewall reload.
dsoxlab start modules-rhel-firewalld -
sysctl module: persisted kernel parameters
Change kernel parameters (ip_forward, tcp_syncookies, swappiness) with immediate effect and persistence through /etc/sysctl.d/.
dsoxlab start modules-rhel-sysctl -
selinux module: modes, booleans and contexts
Switch SELinux to enforcing, flip a boolean persistently, and set a custom context with sefcontext followed by restorecon.
dsoxlab start modules-rhel-selinux -
mount module: manage fstab and mount points
Handle the 5 mount states, mount a loop device, set noatime/nodev/nosuid options, and make a mount survive a reboot.
dsoxlab start modules-rhel-mount -
parted module: create a disk partition
Create aligned MBR or GPT partitions idempotently, set flags (lvm, boot, esp), and inspect the existing partition table.
dsoxlab start modules-rhel-parted -
filesystem module: create a filesystem
Format partitions as ext4 and xfs, pick the right fstype for the use case, and force a rebuild with force: true.
dsoxlab start modules-rhel-filesystem -
LVM: chaining lvg, lvol, filesystem and mount
Build a PV, VG and LV on a spare disk, format it, mount it through fstab, and grow it online with resizefs.
dsoxlab start modules-rhel-lvm-storage
Network modules
-
get_url module: download a file over HTTP/HTTPS
Download a file onto the managed node idempotently, check its integrity with a sha256 checksum, and authenticate the request.
dsoxlab start modules-reseau-get-url -
uri module: REST API calls
Call a REST API with GET/POST and a JSON body, accept several status_code values, and loop a healthcheck with until/retries.
dsoxlab start modules-reseau-uri
Diagnostic modules
-
stat module: inspect files and directories
Check existence, type, size, mode and checksum of a file without modifying it, to drive conditional logic.
dsoxlab start modules-diagnostic-stat -
find module: multi-file search
Search files by glob, regex, age, size and type, then chain loop and file for a targeted, idempotent cleanup.
dsoxlab start modules-diagnostic-find -
assert and fail modules: defensive validation
Validate prerequisites at the start of a play, customize fail_msg and success_msg, and fail explicitly on an error branch.
dsoxlab start modules-diagnostic-assert-fail -
wait_for and pause modules: synchronization
Wait for a TCP port to open or close, for a file or a regex to appear, and add a timed or interactive pause.
dsoxlab start modules-diagnostic-wait-for-pause
Inventories
-
Write a static inventory from scratch: groups, children and group vars
Author an Ansible static inventory by hand: declare host groups, a parent group with children, and group variables, then prove the resolved inventory with ansible-inventory and ansible -m ping.
dsoxlab start inventaires-statiques -
group_vars and host_vars: structured inventory variables
Spread inventory variables across all, group and host levels, then check the resolved value with ansible-inventory --host.
dsoxlab start inventaires-group-vars-host-vars -
Host patterns: wildcards and the :, & and ! operators
Target exactly the hosts you want with --limit and the union, intersection and exclusion operators, without touching the playbook.
dsoxlab start inventaires-patterns-hotes -
Dynamic KVM inventory with community.libvirt
Discover libvirt VMs automatically through the inventory plugin, build Jinja groups and keyed_groups, with no manual inventory.
dsoxlab start inventaires-dynamique-kvm
Roles
-
Create your first role with ansible-galaxy role init
Scaffold a webserver role, fill in tasks, defaults, handlers and meta, then call it from a playbook with roles:.
dsoxlab start roles-creer-premier-role -
Role variables: defaults/ vs vars/ and precedence
Split variables between defaults/ (overridable) and vars/ (internal), wire them into tasks and a Jinja2 template, then verify precedence.
dsoxlab start roles-variables-defaults-vars -
Role handlers and meta: notify and galaxy_info
Write several handlers (service and non-service), trigger them with notify, choose between restarted and reloaded, then complete meta/main.yml for Galaxy.
dsoxlab start roles-handlers-meta -
argument_specs: validate a role's input variables
Write meta/argument_specs.yml to type, constrain and document input variables, then watch an invalid input get rejected automatically.
dsoxlab start roles-argument-specs -
Consume a role: roles:, import_role, include_role
Call the same role three different ways and choose between static and dynamic, especially once a when: is involved.
dsoxlab start roles-consommer-role -
Role dependencies via meta/main.yml
Chain roles with dependencies:, pass them variables, control execution order and dodge the diamond trap with allow_duplicates.
dsoxlab start roles-dependencies -
RHEL system roles: converge time synchronisation with timesync
Consume a vendor role from linux-system-roles: drive chronyd on db1.lab through timesync variables, never writing chrony.conf by hand.
dsoxlab start roles-system-roles
Galaxy and publishing
-
ansible-galaxy CLI: init, install, list, build, publish
Init a role and a collection, install from Galaxy or Git, list what is present, build a tarball and publish it with an API token.
dsoxlab start galaxy-ansible-galaxy-cli -
Install roles and collections from Galaxy or Git
Write a requirements.yml mixing Galaxy roles, Git sources and collections, pin every version and vendor a role into the project for reproducible installs.
dsoxlab start galaxy-installer-roles
Ansible Vault
-
Ansible Vault: encrypt your first secrets file
Encrypt a YAML file with ansible-vault, view it, edit it, consume it from a playbook, then rekey and decrypt it.
dsoxlab start vault-introduction -
encrypt_string vs whole-file encryption
Encrypt a single value with encrypt_string, mix it with plaintext variables via the !vault tag, and choose between inline and whole-file.
dsoxlab start vault-chiffrer-fichier-variable -
Multiple vault-ids: isolate dev, staging and prod
Encrypt each environment with a labelled vault-id, decrypt several vault-ids in a single run and lay out group_vars per environment.
dsoxlab start vault-id-multiples -
Mixed playbooks: main.yml plus vault.yml per group
Split public variables from secrets inside each group_vars, adopt the vault_* convention, and confirm Ansible merges both files transparently.
dsoxlab start vault-playbooks-mixtes -
Vault inside a role: plaintext defaults, encrypted vars
Expose public variables in defaults/main.yml pointing to vault_* values in an encrypted vars/main.yml, and override them from the playbook.
dsoxlab start vault-dans-roles -
Fetch secrets from HashiCorp Vault or OpenBao
Start a local Vault, store a secret in it, read it from Ansible with community.hashi_vault, and compare token, AppRole and JWT authentication.
dsoxlab start vault-integration-hashicorp -
Fetch secrets from Passbolt
Start a local Passbolt CE, authenticate with an OpenPGP key, read a secret from Ansible and compare the Passbolt model with HashiCorp Vault.
dsoxlab start vault-integration-passbolt
Troubleshooting
-
Verbosity levels and callback plugins
Pick the -v level that matches the symptom, enable profile_tasks and YAML output (callback_result_format), and see what a missing no_log leaks.
dsoxlab start troubleshooting-verbosite -
Interactive debugger: debugger: on_failed
Enable the debugger on failure, inspect task, task_vars and result in the REPL, patch arguments on the fly then replay the task with redo.
dsoxlab start troubleshooting-debugger -
Fix broken idempotence and tune performance
Make a shell task idempotent with creates or changed_when, measure a baseline with profile_tasks, then enable pipelining, forks and ControlPersist.
dsoxlab start troubleshooting-idempotence-perfs
Collections
-
Explore Ansible collections: FQCN and structure
List and inspect installed collections, read a galaxy.yml, walk the plugins/roles/playbooks layout and track down a module by its FQCN.
dsoxlab start collections-decouvrir -
Automation content navigator: discover a module in a collection and use it
Use ansible-navigator to find a module inside an installed collection, apply it to produce a verifiable kernel state on db1.lab, and validate an inventory with ansible-navigator inventory.
dsoxlab start collections-navigator -
requirements.yml: multi-source install with GPG signatures
Declare four sources in a requirements.yml, pin every version, check integrity with collection verify and target several Galaxy servers.
dsoxlab start collections-requirements
Advanced practices
-
Version your playbooks with Git
Initialize a Git repository for your playbooks, track and commit them, then push to a local bare remote: the exact EX294 gesture, with no forge to set up.
dsoxlab start pratiques-versionner-git
RHCE EX294 exam
-
RHCE EX294 mock exam: 19 tasks in 4 hours
Work through 19 timed tasks covering inventories, variables, vault, files, packages, services, roles, error handling, rolling waves, delegation, tags, scheduled jobs, custom facts and content collections, each validated by pytest.
dsoxlab start rhce-mock-ex294 -
Mock RHCE EX294 #2
A second full timed EX294 mock: the same 19 categories as mock #1, but every concrete value differs (Apache/valkey stack, different users, LVM layout, ports, SELinux boolean, cron schedule, collection), so nothing can be copied from memory. Each task is proven by pytest.
dsoxlab start rhce-mock-ex294-2