Containerizing WordPress with Docker Compose
The website went down last Tuesday because someone ran apt upgrade and PHP broke. Gerald’s exact words: “Can’t you just put it in a box so this doesn’t happen?” You are not sure he understands containers, but he is not wrong.
Last lab, you installed Apache, PHP, MariaDB, and WordPress by hand: package by package, config file by config file. It took the entire session, and if you needed to do it again on a second server, you would have to repeat every step. Containers solve this problem. A container packages an application and all its dependencies into a single, portable unit that runs the same way everywhere. Docker is the tool that builds and runs containers, and Docker Compose lets you define multi-container applications (like WordPress + its database) in a single file. In this lab, you will replicate your entire Lab 2 setup in about ten lines of configuration.
Before You Start
Section titled “Before You Start”You need:
- An AWS Academy Learner Lab environment
- An SSH client on your laptop
Containers vs. Virtual Machines
Section titled “Containers vs. Virtual Machines”Before diving in, it helps to understand how containers differ from Virtual Machines (VMs). A VM runs a full operating system on top of a hypervisor: it includes its own kernel, init system, and package manager. A container, by contrast, shares the host operating system’s kernel and only packages the application and its libraries. This makes containers much lighter (megabytes vs. gigabytes), faster to start (seconds vs. minutes), and more efficient with resources.
The trade-off is isolation: VMs provide stronger boundaries (separate kernels), while containers share a kernel and rely on Linux namespace and cgroup features for isolation.
Questions
Section titled “Questions”Watch for the answers to these questions as you follow the tutorial.
- How many services are defined in your
docker-compose.yml? What are their names and image tags? (3 points) - After
docker compose down && docker compose up -d, does your blog post still exist? What does this tell you about whatdocker compose downdoes to named volumes? (5 points) - After
docker compose down -v && docker compose up -d, does your blog post still exist? What does the-vflag do, and why is this dangerous in production? (5 points) - After rebooting the EC2 instance, are the containers running? Write down the uptime shown by
docker ps. What restart policy makes this possible? (4 points) - What exact WordPress version is running inside the container? (Hint: check
wp-includes/version.phpwithdocker exec.) (3 points) - Get your TA’s initials showing WordPress loaded in your browser with your named blog post visible. (5 points)
Tutorial
Section titled “Tutorial”Launching an Instance and Installing Docker
Section titled “Launching an Instance and Installing Docker”-
Launch a fresh EC2 instance
In the AWS Console, launch an Ubuntu Server 24.04 LTS instance (t3.micro). Use a Security Group that allows SSH (port 22) and HTTP (port 80). Connect via SSH:
Terminal window ssh -i ~/Downloads/cs312-key.pem ubuntu@<your-public-ip> -
Install Docker Engine
Docker Engine is the runtime that builds and runs containers. Install it using Docker’s official convenience script:
Terminal window curl -fsSL https://get.docker.com | sudo shThis script detects your Linux distribution and installs Docker Engine, the Docker CLI (Command-Line Interface), and the containerd runtime.
-
Add your user to the docker group
By default, Docker commands require
sudo. Adding your user to thedockergroup lets you run Docker commands without it:Terminal window sudo usermod -aG docker ubuntuLog out and back in for the group change to take effect:
Terminal window exitThen reconnect via SSH.
-
Verify Docker is working
Terminal window docker --versiondocker run --rm hello-worldThe
hello-worldcontainer should print a success message, confirming Docker can pull images and run containers.
Writing the Docker Compose File
Section titled “Writing the Docker Compose File”Docker Compose is a tool for defining and running multi-container applications. You describe your services, networks, and volumes in a YAML (YAML Ain’t Markup Language) file, and Compose handles creating and connecting everything.
-
Create a project directory
Terminal window mkdir ~/wordpress-lab && cd ~/wordpress-lab -
Create an environment file for credentials
Hardcoding passwords in your Compose file is bad practice. Instead, store them in a
.envfile that Compose reads automatically:Terminal window vim .envAdd the following (choose your own passwords):
MYSQL_ROOT_PASSWORD=rootpass123MYSQL_DATABASE=wordpressMYSQL_USER=wp_userMYSQL_PASSWORD=wppass456 -
Write the Compose file
Terminal window vim docker-compose.ymlAdd the following:
services:db:image: mariadb:11restart: unless-stoppedenv_file: .envvolumes:- db_data:/var/lib/mysqlwordpress:image: wordpress:6restart: unless-stoppedports:- "80:80"environment:WORDPRESS_DB_HOST: dbWORDPRESS_DB_USER: ${MYSQL_USER}WORDPRESS_DB_PASSWORD: ${MYSQL_PASSWORD}WORDPRESS_DB_NAME: ${MYSQL_DATABASE}volumes:- wp_content:/var/www/html/wp-contentdepends_on:- dbvolumes:db_data:wp_content:Let’s break this down:
servicesdefines two containers:db(MariaDB) andwordpress.imagespecifies which Docker image to use. The tag after the colon pins a major version.restart: unless-stoppedtells Docker to automatically restart the container if it crashes or the host reboots, unless you explicitly stop it withdocker compose stop.ports: "80:80"maps port 80 on the host to port 80 in the container.volumesmounts named volumes into the container. Named volumes persist data even when containers are removed.depends_onensures the database starts before WordPress.- The
volumes:section at the bottom declares the named volumes.
Running the Stack
Section titled “Running the Stack”-
Start the containers
Terminal window docker compose up -dThe
-dflag runs containers in detached mode (in the background). Docker will pull the MariaDB and WordPress images if you do not have them locally, then start both containers. This may take a minute on the first run. -
Check the containers are running
Terminal window docker compose psYou should see both
dbandwordpresswith state “Up.” -
Access WordPress in your browser
Open
http://<your-public-ip>in your browser. You should see the WordPress installation wizard, the same one you saw in Lab 2, but this time you did not install Apache, PHP, or MariaDB manually. Complete the WordPress setup and create a blog post titled with your name and today’s date (e.g., “Jane Smith - 2026-03-10”). You will use this post to test data persistence.
Testing Data Persistence
Section titled “Testing Data Persistence”Volumes are the mechanism Docker uses to persist data beyond the life of a container. Understanding when data survives and when it does not is critical for operating containerized services.
-
Stop and restart without removing volumes
Terminal window docker compose downdocker compose up -dThe
downcommand stops and removes the containers, but named volumes are preserved by default. After restarting, check that your blog post still exists by visitinghttp://<your-public-ip>in your browser.List the volumes:
Terminal window docker volume lsNote the exact volume names; they will be prefixed with your project directory name (e.g.,
wordpress-lab_db_data). -
Stop and restart WITH volume removal
Now run
downwith the-vflag, which removes named volumes:Terminal window docker compose down -vdocker compose up -dVisit
http://<your-public-ip>again. You should see the WordPress installation wizard, as if it were a fresh install. Your blog post is gone, because the database volume was deleted and recreated empty.List the volumes again:
Terminal window docker volume lsThe volume names are the same, but these are new, empty volumes. Compare this output to the previous step.
-
Recreate your blog post
Complete the WordPress setup again and create a new blog post with your name and today’s date. You will need this post for the remaining steps.
Testing Restart Policies
Section titled “Testing Restart Policies”-
Verify the restart policy
Your Compose file already specifies
restart: unless-stopped. This means Docker will automatically restart your containers if the EC2 instance reboots. -
Reboot the instance
Terminal window sudo rebootWait about 30 seconds, then reconnect via SSH.
-
Check that containers restarted
Terminal window docker ps --format "table {{.Names}}\t{{.Status}}"You should see both containers running, with an uptime showing how long ago they started after the reboot. If the reboot was 60 seconds ago, you might see “Up 45 seconds.”
-
Verify your data survived
Visit
http://<your-public-ip>and confirm your blog post is still there. The combination of named volumes (for data persistence) and restart policies (for automatic recovery) means your service survives a reboot without intervention.
Exploring Inside a Container
Section titled “Exploring Inside a Container”-
Open a shell inside the WordPress container
The
docker execcommand runs a command inside a running container. The-itflags give you an interactive terminal:Terminal window docker exec -it $(docker compose ps -q wordpress) bashYou are now inside the WordPress container. Notice that this is a minimal Linux environment; many commands you are used to (like
vimorsudo) are not installed. The container includes only what WordPress needs to run. -
Find the WordPress version
Terminal window cat /var/www/html/wp-includes/version.php | grep wp_versionRecord the exact version string for your lab questions.
-
Exit the container
Terminal window exit
Clean Up
Section titled “Clean Up”When you are done with this lab, stop the containers to avoid unnecessary resource usage:
docker compose downIf you want to preserve the data volumes for a future session, do not add -v. If you are done with this lab permanently, you can also terminate the EC2 instance.
You have now replaced an entire manual LAMP stack setup with a single Compose file. The key lessons: containers are portable and reproducible, volumes persist data across container restarts, and restart policies provide basic self-healing. In the next lab, you will push container images to a private registry and practice backup and restore workflows.