Deploying a WordPress Website with Ansible

Level 1 – Single VM with Caddy Reverse Proxy

In this article, we will deploy a simple and automated WordPress infrastructure using Ansible. The goal is to demonstrate how to deploy a website in a reproducible way, similar to how a DevOps engineer would manage infrastructure in production. For this Level 1 architecture, we intentionally keep things simple:

  • 1 Linux VM

  • Docker

  • WordPress

  • MariaDB

  • Caddy as reverse proxy with automatic HTTPS

Everything is fully deployed using Ansible automation. This approach allows you to:

  • avoid manual installations

  • version your infrastructure in Git

  • reproduce environments easily

Each directory has a specific responsibility within the automation workflow.

Understanding this structure is the first step to learning how to organize real-world Ansible projects.

Project Architecture

The project is organized as an infrastructure repository.

devops-wp-caddy/

├── ansible/
│ ├── inventory/
│ │ └── production.ini
│ │
│ ├── playbooks/
│ │ └── site.yml
│ │
│ ├── roles/
│ │ ├── common/
│ │ ├── docker/
│ │ ├── wordpress_backend/
│ │ └── caddy_proxy/
│ │
│ └── group_vars/
│ └── all.yml

├── deploy/
│ ├── compose.yml
│ └── Caddyfile

└── README.md

The inventory Directory

ansible/inventory/
└── production.ini

The inventory defines the machines that Ansible will manage.

Example:

[web] vm-prod ansible_host=10.0.0.10 ansible_user=devops

This means:

  • vm-prod is a server managed by Ansible

  • its IP address is 10.0.0.10

  • the SSH user is devops

When running a playbook, Ansible will:

  1. connect via SSH

  2. execute the defined tasks on the target host

The group_vars Directory

ansible/group_vars/
└── all.yml

This directory contains global variables used across the roles.

Example:

domain: lavallee.tech
wordpress_port: 8080
db_name: wordpress
db_user: wpuser
db_password: securepassword

Using variables allows you to:

  • centralize configuration

  • avoid modifying role code directly

  • easily adapt infrastructure settings

In more advanced setups, sensitive values like passwords are stored using Ansible Vault.

The playbooks Directory

ansible/playbooks/
└── site.yml

The playbook is the main entry point of the automation.

Example:

– hosts: web
become: true

roles:
– common
– docker
– wordpress_backend
– caddy_proxy

This file tells Ansible:

  1. which hosts to target

  2. which roles to execute

  3. in which order they should run

To deploy the infrastructure, run:

ansible-playbook -i inventory/production.ini playbooks/site.yml

Ansible will execute each role sequentially.

The roles Directory

Roles are the core building blocks of an Ansible project.

They allow infrastructure to be modular and reusable.

Each role represents a specific responsibility:

roles/
├── common
├── docker
├── wordpress_backend
└── caddy_proxy

The common Role

This role prepares the system.

Typical tasks include:

  • updating the system

  • installing base packages

  • preparing the environment

Structure:

roles/common/
├── tasks/
│ └── main.yml

Example task:

– name: Install base packages
apt:
name:
– curl
– git
– vim
state: present

The docker Role

This role installs and configures Docker on the VM.

Structure:

roles/docker/
├── tasks/
│ └── main.yml

Typical tasks:

  • add Docker repository

  • install Docker engine

  • enable and start the Docker service

Example:

– name: Install docker
apt:
name: docker.io
state: present

The wordpress_backend Role

This role deploys the WordPress application using Docker.

Structure:

roles/wordpress_backend/
├── tasks/
│ └── main.yml
├── templates/
│ └── compose.yml.j2

Templates use Jinja2, allowing configuration files to be generated dynamically.

For example, a docker-compose.yml file.

Example:

services:

db:
image: mariadb
environment:
MYSQL_DATABASE:
MYSQL_USER:
MYSQL_PASSWORD:

wordpress:
image: wordpress
ports:
":80"

The variables are injected from group_vars.

The caddy_proxy Role

Caddy is a modern reverse proxy that automatically manages HTTPS certificates.

Structure:

roles/caddy_proxy/
├── tasks/
│ └── main.yml
└── templates/
└── Caddyfile.j2

Example Caddy configuration:

Caddy automatically:

  • obtains Let's Encrypt certificates

  • handles HTTPS configuration

  • forwards requests to the WordPress container

The deploy Directory

deploy/
├── compose.yml
└── Caddyfile

This directory contains the configuration files used by Docker and the reverse proxy.

In some projects, these files are:

  • generated dynamically via Ansible templates

  • copied to the target server during deployment

Typical files include:

  • docker-compose.yml

  • Caddyfile

Full Deployment

Once everything is configured, the entire infrastructure can be deployed with a single command:

ansible-playbook -i ansible/inventory/production.ini ansible/playbooks/site.yml

Ansible will:

  1. connect to the VM

  2. install Docker

  3. deploy WordPress containers

  4. configure Caddy

  5. enable HTTPS automatically