Automate a Web Server with Ansible

Use Ansible to automatically install Nginx and deploy a custom web page.

Introduction

30 Second Summary

Every time a company launches a new server, someone has to install software, tweak settings, and deploy files. Doing this manually for one server is tedious. Doing it for ten is a nightmare.

In this project, you will use Ansible to fully automate the configuration of a web server running inside a Docker container. You will go from a bare server to a live custom web page, deployed with a single command.

What You'll Build

You type one command, walk away, and come back to a fully configured server with a custom web page live in your browser.

By the end of this project, you'll have:

  • A working Ansible playbook that installs Nginx and deploys a web page to a server with a single command.
  • A custom web page you can visit at localhost:8080 in your browser, built entirely through automation.
  • A reusable template system where you change a variable, re-run the playbook, and the deployed page updates automatically.
  • Secret Mission: Add a second server and deploy different content to each using the same playbook.

Are there any prerequisites?

You should be comfortable with basic terminal commands (cd, ls, running scripts). Familiarity with Docker concepts like containers and images is helpful, but you do not need to be an expert.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters.

Set Up Your Ansible Lab

Ansible needs two things to work: a control node (the machine that runs Ansible commands) and one or more target servers (the machines Ansible configures).

Setting up real virtual machines is overkill for learning. Instead, your WSL terminal will be the control node, and a Docker container will simulate a target server. In this step, you will install Ansible and spin up that target container.

In this step, get ready to:
  • Install Python 3.12+ and Ansible in your WSL terminal.
  • Create and start a Docker container that acts as your target server.
  • Verify both Ansible and the container are working.
Install Python and Ansible in WSL

Ansible is a Python application. It requires Python 3.12 or higher on the control node. Your WSL Ubuntu distribution likely ships with Python 3.12 already, but let's confirm.

  • In your WSL terminal, check your Python version by running this command:
python3 --version

✔️ I see version 3.12 or higher

Your Python version meets the requirement. Move on to installing pipx below.

ⓧ I see a version lower than 3.12

Your WSL distribution ships an older Python. You need to upgrade to Ubuntu 24.04 which includes Python 3.12.

  • Open a PowerShell window on Windows (not WSL) and install Ubuntu 24.04 by running:
wsl --install -d Ubuntu-24.04
  • Open the new Ubuntu 24.04 terminal after the install completes.
  • Re-run python3 --version to confirm you see 3.12 or higher.

ⓧ Command not found

Python 3 is not installed in your WSL distribution. Install it with your package manager.

  • Install Python 3 by running these commands:
sudo apt update
sudo apt install -y python3
  • Run python3 --version again to confirm you see 3.12 or higher. If the version is below 3.12, follow the steps in the "I see a version lower than 3.12" tab above.

Now you need pipx, a tool that installs Python applications in isolated environments. This keeps Ansible's dependencies separate from your system Python.

  • Install pipx by running these commands:
sudo apt update
sudo apt install -y pipx
pipx ensurepath

What do these commands do?

  • sudo apt install -y pipx installs pipx from the Ubuntu package repository.
  • pipx ensurepath adds the pipx binary directory to your PATH so you can run tools that pipx installs from anywhere.
  • Close and reopen your WSL terminal so the PATH change takes effect.
  • Install Ansible by running this command:
pipx install ansible-core
  • Verify Ansible installed successfully by running:
ansible --version

You should see version information starting with ansible followed by a version number.

Seeing 'command not found' after installing?

  • Make sure you closed and reopened your WSL terminal after running pipx ensurepath. The PATH update only takes effect in a new session.
  • If you still get the error, run source ~/.bashrc to reload your shell configuration without closing the terminal.

Help me troubleshoot my Ansible installation in WSL.

Create your target server container

Your target server will be a Docker container running Ubuntu with SSH enabled. Ansible connects to servers over SSH, so the container needs an SSH server running inside it.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir ansible-lab
cd ansible-lab
  • Create the docker subdirectory for your Dockerfile by running:
mkdir docker
  • Confirm the folder structure by running:
ls

You should see docker listed in the output.

  • Open docker/Dockerfile in your text editor and paste this code:
FROM ubuntu:24.04

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && \
    apt-get install -y openssh-server python3 python3-apt && \
    mkdir -p /var/run/sshd && \
    echo 'root:ansible' | chpasswd && \
    sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config

EXPOSE 22 80

CMD ["/usr/sbin/sshd", "-D"]

What does this Dockerfile do?

  • Starts from a clean Ubuntu 24.04 image.
  • Installs an SSH server and Python 3 (Ansible modules need Python on the target).
  • Sets the root password to ansible so Ansible can log in over SSH.
  • Enables root login over SSH (necessary for this lab environment).
  • Runs the SSH daemon as its main process so the container stays alive.
  • Save docker/Dockerfile.
  • Now create docker-compose.yml in your ansible-lab folder (the project root). Open it in your editor and paste this code:
services:
  web:
    build: ./docker
    container_name: ansible-web
    ports:
      - "2222:22"
      - "8080:80"

What does this compose file do?

  • Builds the Docker image from your docker/Dockerfile.
  • Names the container ansible-web.
  • Maps port 2222 on your machine to port 22 (SSH) inside the container.
  • Maps port 8080 on your machine to port 80 (HTTP) inside the container for the web server you will install later.
  • Save docker-compose.yml.
  • Build and start your target server container by running:
docker compose up -d --build

What does this command do?

  • docker compose up creates and starts all services defined in your compose file.
  • -d runs the container in detached mode (in the background).
  • --build forces Docker to build the image from your Dockerfile before starting.

Docker will download the Ubuntu 24.04 base image, install the packages, and start the container. This may take a minute on first run.

Seeing a Docker error?

  • Make sure Docker Desktop is running on Windows. Check your system tray for the Docker whale icon.
  • If you see a permissions error, make sure WSL integration is enabled in Docker Desktop under Settings, then Resources, then WSL integration.

Help me fix Docker not working in my WSL terminal.

Verify your lab environment

Before you run the next command, predict what you expect to see. What container name and port mappings should appear?

  • Check that your container is running by executing:
docker ps

You should see a container named ansible-web with ports 0.0.0.0:2222->22/tcp and 0.0.0.0:8080->80/tcp listed.

  • Confirm Ansible is still accessible by running:
ansible --version

You should see the Ansible version output confirming your installation is working.

✔️ Awesome, I've got everything!

Great. You have a working control node (WSL with Ansible) and a target server (the Docker container). Double check you have saved both files.

ⓧ I'd like to double check the full code

Confirm your two files match the code below exactly.

docker/Dockerfile:

FROM ubuntu:24.04

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && \
    apt-get install -y openssh-server python3 python3-apt && \
    mkdir -p /var/run/sshd && \
    echo 'root:ansible' | chpasswd && \
    sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config

EXPOSE 22 80

CMD ["/usr/sbin/sshd", "-D"]

docker-compose.yml:

services:
  web:
    build: ./docker
    container_name: ansible-web
    ports:
      - "2222:22"
      - "8080:80"

Your lab is ready. You have Ansible installed on your control node and a target server waiting for commands. Next up, you will connect Ansible to that server so it can start managing it.

Connect Ansible to Your Server

Your Docker container is running and Ansible is installed. But Ansible doesn't know your server exists yet. Before you can automate anything, you need to tell Ansible which servers to manage and how to reach them.

You could pass connection details as command-line flags every time you run a command. That gets tedious fast. Instead, you'll create two small configuration files that store this information once and for all.

In this step, get ready to:
  • Create an inventory file listing your server's connection details.
  • Create an ansible.cfg so you don't have to repeat settings every time.
  • Run ad-hoc commands to prove Ansible can reach and control the server.
Create your inventory file

Ansible's inventory is a file that lists every server you want to manage. Each entry includes the connection details Ansible needs: the host address, SSH port, username, and password.

  • Create a file called inventory.ini in your project folder by running:
cat > inventory.ini << 'EOF'
[webservers]
web1 ansible_host=127.0.0.1 ansible_port=2222 ansible_user=root ansible_password=ansible
EOF

What does this file do?

  • [webservers] creates a group name. You can target all servers in this group with a single command.
  • web1 is the alias for your server. Ansible uses this name in its output.
  • ansible_host=127.0.0.1 and ansible_port=2222 tell Ansible where to connect. Port 2222 on your host maps to port 22 inside the container.
  • ansible_user=root and ansible_password=ansible provide the SSH credentials you set in the Dockerfile.
  • Confirm the file was created by running:
cat inventory.ini

You should see the two lines you just wrote: the group header and the server entry beneath it.

See "No such file or directory"?

  • Make sure you're in your project directory (the same folder that contains docker-compose.yml).
  • Re-run the cat > inventory.ini command above. Copy it exactly, including the EOF markers.

Help me troubleshoot why my inventory.ini file wasn't created.

Create your Ansible configuration file

Without a configuration file, you'd need to pass flags like -i inventory.ini on every command. The ansible.cfg file sets default behaviors so Ansible picks up your inventory automatically.

  • Create ansible.cfg in your project folder by running:
cat > ansible.cfg << 'EOF'
[defaults]
inventory = inventory.ini
host_key_checking = False
EOF

What do these settings do?

  • inventory = inventory.ini tells Ansible to always use your inventory file without needing a -i flag.
  • host_key_checking = False disables SSH host key verification. Docker containers regenerate their SSH keys every time they rebuild, so this prevents "host key changed" errors during development.
  • Confirm both config files exist by running:
ls

You should see ansible.cfg, docker-compose.yml, docker, and inventory.ini in the output.

Missing ansible.cfg in the listing?

  • Run the cat > ansible.cfg command again. Make sure you include both EOF markers (the opening one after << and the closing one on its own line).
  • Check that you're in the correct directory with pwd. You should see your project folder path.

Help me figure out why my ansible.cfg wasn't created.

✔️ Awesome, I've got everything!

Great. Make sure both files are saved. Next you'll use them to talk to your server.

ⓧ I'd like to double check the full code

Here's what each file should contain:

inventory.ini:

[webservers]
web1 ansible_host=127.0.0.1 ansible_port=2222 ansible_user=root ansible_password=ansible

ansible.cfg:

[defaults]
inventory = inventory.ini
host_key_checking = False
Test the connection with ad-hoc commands

Ad-hoc commands are one-off Ansible instructions you run directly from the terminal. They're perfect for quick checks before you build anything more complex.

What is an ad-hoc command?

The syntax is ansible [group] -m [module]. You pick a group from your inventory and a module (a built-in Ansible action) to run against it. The ping module doesn't send a network ping. It checks that Ansible can connect via SSH and run Python on the target.

Before you run this next command, what do you think will happen when Ansible tries to reach the container?

  • Ping your server by running:
ansible webservers -m ping

You should see a green SUCCESS response with "ping": "pong" from web1. This confirms Ansible connected to your container over SSH and executed successfully.

Seeing "UNREACHABLE" or a connection error?

  • Check your container is running with docker ps. You should see ansible-web in the list.
  • Verify the port mapping shows 0.0.0.0:2222->22/tcp in the docker ps output.
  • Double-check inventory.ini has ansible_port=2222 and ansible_password=ansible with no typos.

Help me debug why Ansible can't reach my Docker container.

Now try running an actual command on the server to prove Ansible can do more than just connect.

  • Check the server's hostname by running:
ansible webservers -m shell -a "hostname"

You should see the container's hostname in the output. Ansible connected, ran the command remotely, and returned the result to you.

Why does this matter?

You just controlled a remote server without manually SSHing into it. Ansible handled the connection, executed the command, and reported back.

But notice the limitation: each ad-hoc command does one thing at a time. Imagine needing to install software, copy files, start services, and configure permissions in exact order. Running 10 separate ad-hoc commands would be tedious and error-prone. That's exactly the problem playbooks solve in the next step.

Your connection is solid and Ansible can execute commands on your server. But running tasks one at a time doesn't scale. Next up, you'll write a playbook that runs multiple tasks in sequence with a single command.

Write Your First Playbook

In the last step, you proved that Ansible can reach your server and run commands on it. Each ad-hoc command worked, but you had to type them one at a time.

Configuring a real server means running many tasks in the right order. A playbook lets you define all those tasks in a single YAML file that you can run repeatedly, share with your team, and version-control in Git.

In this step, get ready to:
  • Write a playbook that installs Nginx and starts it.
  • Run the playbook and observe Ansible executing each task in order.
  • Re-run the playbook and confirm idempotency.
Write a playbook that installs Nginx and starts it

A playbook is a YAML file that describes the desired state of your server. You list tasks in order, and Ansible executes them top to bottom.

  • Create a file called playbook.yml in your project folder (the same folder that holds inventory.ini) by running:
touch playbook.yml
  • Confirm the file exists by running:
ls

You should see playbook.yml listed alongside inventory.ini, ansible.cfg, and docker-compose.yml.

  • Open playbook.yml in your text editor and paste the following content:
---
- name: Configure web server
  hosts: webservers
  vars:
    page_title: "Deployed by Ansible"
    page_message: "This server was configured automatically!"

  tasks:
    - name: Install Nginx
      ansible.builtin.apt:
        name: nginx
        state: present
        update_cache: true

    - name: Start Nginx
      ansible.builtin.shell: service nginx start
      args:
        creates: /run/nginx.pid

What does this playbook do?

  • name: Configure web server gives the play a human-readable label that shows up in output.
  • hosts: webservers tells Ansible to run against the [webservers] group from your inventory.
  • vars defines variables you will use in the next step to deploy a custom web page.
  • The first task uses the ansible.builtin.apt module to install Nginx and refresh the package cache.
  • The second task starts Nginx using service nginx start. The creates: /run/nginx.pid argument makes it idempotent. Ansible skips the task if that file already exists.
  • Save playbook.yml.

Why use fully qualified module names?

Names like ansible.builtin.apt are called fully qualified collection names (FQCNs).

They make it clear which collection provides the module and prevent name collisions as your playbooks grow.

✔️ Awesome, I've got everything!

Great. Double check you have saved playbook.yml before moving on.

ⓧ I'd like to double check the full code

---
- name: Configure web server
  hosts: webservers
  vars:
    page_title: "Deployed by Ansible"
    page_message: "This server was configured automatically!"

  tasks:
    - name: Install Nginx
      ansible.builtin.apt:
        name: nginx
        state: present
        update_cache: true

    - name: Start Nginx
      ansible.builtin.shell: service nginx start
      args:
        creates: /run/nginx.pid
Run the playbook and observe each task

Your playbook is ready. Before you run it, what do you think Ansible will do with the two tasks you defined?

  • Run your playbook by executing this command:
ansible-playbook playbook.yml

Ansible connects to the container, installs Nginx, then starts the service. You should see each task listed with a status of changed in yellow text.

What does the output mean?

  • ok means the task ran and the system was already in the desired state.
  • changed means Ansible made a modification to reach the desired state.
  • The PLAY RECAP at the bottom summarises how many tasks were ok, changed, or failed across all hosts.
  • Open your web browser and visit http://localhost:8080.

You should see the default Nginx welcome page with the heading "Welcome to nginx!" confirming the web server is running inside your container.

Don't see the welcome page?

  • Make sure your Docker container is still running. Run docker ps and confirm ansible-web is listed.
  • Check that port 8080 is not in use by another application on your machine.
  • If the playbook showed an error on the Install Nginx task, your container may not have internet access. Run docker compose down && docker compose up -d to recreate it, then re-run the playbook.

Still stuck? Help me troubleshoot why I can't see the Nginx welcome page at localhost:8080 after running my Ansible playbook.

Re-run the playbook and confirm idempotency

One of Ansible's most important properties is idempotency. An idempotent playbook produces the same end state no matter how many times you run it.

Before you run the same command again, what do you think will happen this time? Nginx is already installed and running.

  • Run the playbook a second time:
ansible-playbook playbook.yml

This time, both tasks report ok in green instead of changed in yellow.

Why does this matter?

Ansible checked the server, saw that Nginx was already installed and running, and did nothing. You can run this playbook 10 times and get the same result every time.

This is what makes playbooks safe to re-run in production. They only change what needs changing.

Your playbook installs and starts a web server with a single command. The Nginx welcome page proves the automation works. In the next step, you will replace that generic page with your own custom content using variables and templates.

Deploy a Custom Page with Variables and Templates

Your playbook installs and starts Nginx automatically. That proves the automation works. But right now, every server you configure shows the exact same generic Nginx welcome page.

You need a way to deploy custom content that can differ from server to server. Jinja2 templates solve this. They let you write HTML with placeholder variables that Ansible fills in at deploy time.

In this step, get ready to:
  • Use the variables already defined in your playbook to feed dynamic content into a template.
  • Create a Jinja2 template that generates a custom HTML page.
  • Add a template task and a handler that deploys and reloads the web server.
Review the variables in your playbook

Your playbook.yml already has a vars block from the previous step. These variables define values that Ansible can inject into templates at runtime.

  • Open playbook.yml in your text editor and find the vars section:
  vars:
    page_title: "Deployed by Ansible"
    page_message: "This server was configured automatically!"

What do these variables do?

Variables in Ansible work like containers for reusable values. Instead of hardcoding text directly into files, you define it once in vars and reference it by name wherever you need it.

When you change a variable's value, every reference updates automatically the next time you run the playbook. This is how you configure different servers differently without rewriting your automation.

Create a Jinja2 template for your web page

A Jinja2 template is an HTML file with {{ variable_name }} placeholders. When Ansible deploys the template, it replaces each placeholder with the actual variable value.

  • Create the templates/ directory by running this command:
mkdir templates
  • Confirm the directory was created by running:
ls

You should see templates listed alongside your other project files.

  • Create a new file called index.html.j2 inside the templates/ folder and paste the following content:
<!DOCTYPE html>
<html>
<head>
    <title>{{ page_title }}</title>
    <style>
        body { font-family: Arial, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; }
        h1 { color: #2d6a4f; }
        .info { background: #f0f0f0; padding: 15px; border-radius: 5px; margin-top: 20px; }
    </style>
</head>
<body>
    <h1>{{ page_title }}</h1>
    <p>{{ page_message }}</p>
    <div class="info">
        <p>Server hostname: {{ inventory_hostname }}</p>
        <p>Managed by: Ansible</p>
    </div>
</body>
</html>

What does this template do?

The {{ page_title }} and {{ page_message }} placeholders pull values from the vars block in your playbook. Ansible replaces them with the actual text when it deploys the file.

The {{ inventory_hostname }} placeholder is a built-in Ansible variable. It automatically resolves to the name of the server being configured (web1 in your case, from the inventory file).

  • Save templates/index.html.j2.

File not in the right place?

Make sure the file is at templates/index.html.j2 relative to your project folder. The templates/ directory should be at the same level as playbook.yml. You can confirm by running ls templates/ and seeing index.html.j2 in the output.

Help me check my template file location.

Add a template task and handler to the playbook

Your template exists on disk, but Ansible does not know about it yet. You need a task that tells Ansible to render the template and deploy it to the server. You also need a handler that reloads Nginx when the page content changes.

What is a handler?

A handler is a task that only runs when triggered. Instead of reloading Nginx every time you run the playbook, the handler fires only when the template task reports a change. This keeps your playbook efficient and prevents unnecessary service restarts.

  • In playbook.yml, find the Start Nginx task. Add the following new task directly below it (at the same indentation level as the other tasks):
    - name: Deploy custom web page
      ansible.builtin.template:
        src: templates/index.html.j2
        dest: /var/www/html/index.html
      notify: Reload Nginx
  • Now add a handlers section at the very end of the file, at the same indentation level as tasks:
  handlers:
    - name: Reload Nginx
      ansible.builtin.shell: nginx -s reload

What does this code do?

The ansible.builtin.template module takes your Jinja2 file, fills in all the {{ }} placeholders with real values, and copies the result to /var/www/html/index.html on the server.

The notify: Reload Nginx line triggers the handler only when the deployed file actually changes. The handler uses nginx -s reload to tell Nginx to pick up the new page without fully restarting.

  • Save playbook.yml.

✔️ Awesome, I've got everything!

Double check you have saved both playbook.yml and templates/index.html.j2.

ⓧ I'd like to double check the full code

Here is the complete playbook.yml:

---
- name: Configure web server
  hosts: webservers
  vars:
    page_title: "Deployed by Ansible"
    page_message: "This server was configured automatically!"

  tasks:
    - name: Install Nginx
      ansible.builtin.apt:
        name: nginx
        state: present
        update_cache: true

    - name: Start Nginx
      ansible.builtin.shell: service nginx start
      args:
        creates: /run/nginx.pid

    - name: Deploy custom web page
      ansible.builtin.template:
        src: templates/index.html.j2
        dest: /var/www/html/index.html
      notify: Reload Nginx

  handlers:
    - name: Reload Nginx
      ansible.builtin.shell: nginx -s reload

And the complete templates/index.html.j2:

<!DOCTYPE html>
<html>
<head>
    <title>{{ page_title }}</title>
    <style>
        body { font-family: Arial, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; }
        h1 { color: #2d6a4f; }
        .info { background: #f0f0f0; padding: 15px; border-radius: 5px; margin-top: 20px; }
    </style>
</head>
<body>
    <h1>{{ page_title }}</h1>
    <p>{{ page_message }}</p>
    <div class="info">
        <p>Server hostname: {{ inventory_hostname }}</p>
        <p>Managed by: Ansible</p>
    </div>
</body>
</html>

Before you run the playbook, think about what you expect to happen. Will the Install Nginx and Start Nginx tasks report "changed" or "ok"? What about the new Deploy task?

  • Run the updated playbook:
ansible-playbook playbook.yml

The Install Nginx and Start Nginx tasks report "ok" because nothing changed on the server. The new Deploy custom web page task reports "changed" because it placed a new file. The Reload Nginx handler fires because the template task triggered it.

What is idempotency in action?

Ansible only makes changes when the current state does not match the desired state. Nginx was already installed and running, so those tasks did nothing. The template was new, so that task deployed it. This is idempotency. Run the playbook again and every task will report "ok" because the server already matches your desired state.

  • Open your browser and visit http://localhost:8080.

You should see your custom page with the title "Deployed by Ansible", the message "This server was configured automatically!", and the server hostname "web1".

Still seeing the default Nginx page?

If http://localhost:8080 still shows the generic Nginx welcome page, try a hard refresh (Ctrl+Shift+R) to bypass your browser cache.

If that does not help, check the playbook output for errors on the Deploy task. Make sure templates/index.html.j2 exists in the correct location relative to playbook.yml.

Help me debug why my custom page is not showing up.

You just deployed a fully custom web page using nothing but a single command. Change the page_message variable, re-run the playbook, and the page updates automatically. That is the power of templates and variables working together.

Secret mission

Configure Multiple Servers

Right now your playbook configures one server. In production, you would manage dozens or hundreds. Can you add a second server and deploy different content to each using the same playbook?

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally using Docker containers, so there are no ongoing cloud costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • Docker containers (ansible-web and ansible-web2).
  • Ansible (ansible-core installed via pipx).
  • Project files (docker-compose.yml, playbook.yml, inventory.ini, ansible.cfg, templates/, docker/).

Keep everything running

No action needed. Choose this if you're still actively building or want to keep practising Ansible playbooks against your Docker containers.

  • Your Docker containers will continue running in the background and remain accessible via SSH.
  • You can re-run ansible-playbook playbook.yml any time to test changes.
  • Ansible remains installed and available in your WSL terminal.

Pause - I'll come back to this later

Shut down running containers to free up memory and CPU, but keep everything in place so you can resume later.

  • Stop your Docker containers by running:
docker compose stop
  • When you're ready to resume, start them again by running:
docker compose start
  • Your project files and Ansible installation remain untouched.

Delete - I don't want to use this again

Remove all project resources and start fresh. This removes containers, images, installed tools, and project files.

  • Remove Docker containers and networks by running:
docker compose down
  • Uninstall ansible-core by running:
pipx uninstall ansible-core
  • Navigate to your Desktop and remove the project folder by running:
cd ~/Desktop
rm -rf ansible-lab
  • If you no longer need pipx itself, you can remove it with sudo apt remove pipx.

Nice Work!

Nice Work!

You just automated an entire server configuration with Ansible. One command, and a fresh server gets Nginx installed, started, and serving your custom web page.

Here's what you learned:

  • Wrote an Ansible playbook that installs and starts Nginx on a remote server with a single ansible-playbook command.
  • Used Jinja2 templates and variables to deploy custom, server-specific web pages without rewriting the playbook for each server.
  • Implemented handlers and idempotent tasks so the playbook only makes changes when needed and produces the same result no matter how many times you run it.
  • Secret Mission: Configured multiple servers with different content from a single playbook using host-specific inventory variables.

Ready to quiz yourself?