Image Registry, Backups, and Version Switching (ECR + S3)
Gerald wants a “winter menu” and a “summer menu” version of the website, and the ability to switch between them “like flipping a sign.” Also, his nephew deleted the database again. Gerald would like “a backup, like in the movies, where they say enhance and everything comes back.” You are going to implement versioned images and an actual backup strategy.
In this lab, you will use Amazon Elastic Container Registry (ECR) to host your WordPress images in a private registry, practice switching between versions (and rolling back when things go wrong), and implement a backup and restore workflow using Amazon Simple Storage Service (S3).
Before You Start
Section titled “Before You Start”You need:
- An AWS Academy Learner Lab environment
- An SSH client on your laptop
- Docker installed on an EC2 instance (from Lab 3, or install fresh with a 20 GiB root volume)
Questions
Section titled “Questions”Watch for the answers to these questions as you follow the tutorial.
- Write down the two image tags you pushed to ECR and the approximate push timestamp of each. (4 points)
- What is the first 12 characters of the SHA256 image digest of your v1 image? (Find it in the ECR console or CLI.) (3 points)
- What WordPress version is reported when running the v2 image? What version after rolling back to v1? (4 points)
- What is the file size (in bytes or KB) of your database backup in S3? Write down your S3 bucket name. (4 points)
- After restoring from the S3 backup into a fresh database, does your original blog post exist? What is its title? (5 points)
- What retention period did you configure for your S3 lifecycle rule, and why is a lifecycle rule important for cost control? (3 points)
- Get your TA’s initials showing your ECR repository with both image tags visible in the AWS Console. (2 points)
Tutorial
Section titled “Tutorial”Creating an ECR Repository
Section titled “Creating an ECR Repository”Amazon ECR is a fully managed container image registry. It integrates with AWS Identity and Access Management (IAM) for access control, so only authorized users and services can pull or push images.
-
Install the AWS CLI
SSH into your EC2 instance and install the AWS CLI v2 using the official installer:
Terminal window sudo apt install -y unzipcurl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"unzip awscliv2.zipsudo ./aws/installVerify it works:
Terminal window aws --version -
Configure AWS credentials
The CLI needs credentials to talk to AWS. In AWS Academy Learner Lab, these are temporary and must be copied from the portal each session:
- In the Learner Lab portal, click AWS Details
- Click Show next to AWS CLI
- Copy the credentials block (it contains
aws_access_key_id,aws_secret_access_key, andaws_session_token) - On your EC2 instance, paste them into the credentials file:
Terminal window mkdir -p ~/.awsvim ~/.aws/credentialsThe file should look like this (with your actual values from the portal):
[default]aws_access_key_id=ASIA...aws_secret_access_key=...aws_session_token=...Also set the default region:
Terminal window aws configure set region us-east-1 -
Create a repository via the CLI
Terminal window aws ecr create-repository --repository-name cs312-wordpress-lab --region us-east-1The output will include a
repositoryUrithat looks something like:123456789012.dkr.ecr.us-east-1.amazonaws.com/cs312-wordpress-labNote this URI; you will use it throughout the lab. The number at the beginning is your AWS account ID.
-
Authenticate Docker to ECR
Docker needs credentials to push images to your private registry. The AWS CLI can generate a temporary token:
Terminal window aws ecr get-login-password --region us-east-1 | \docker login --username AWS --password-stdin \<your-account-id>.dkr.ecr.us-east-1.amazonaws.comReplace
<your-account-id>with the number from your repository URI. You should see “Login Succeeded.”
Pushing Two Image Versions
Section titled “Pushing Two Image Versions”Image tagging is how you manage versions in a container registry. A tag is a human-readable label attached to a specific image. Tags like latest are convenient but dangerous in production because they are mutable; anyone can push a new image with the same tag, and you lose track of what is actually running. Pinned tags (like wp-6.4-v1) are explicit and reproducible.
-
Pull and tag version 1
Pull a specific WordPress version from Docker Hub, verify it, tag it for your ECR repository, and push it:
Terminal window docker pull wordpress:6.4.3docker run --rm wordpress:6.4.3 grep wp_version /usr/src/wordpress/wp-includes/version.phpdocker tag wordpress:6.4.3 <your-repo-uri>:wp-6.4-v1docker push <your-repo-uri>:wp-6.4-v1Replace
<your-repo-uri>with your full ECR repository URI. Thegrepline confirms the image actually contains the version you expect before pushing; minor version tags like6.4are floating and can drift to newer patches over time. The WordPress entrypoint only copies files to/var/www/htmlwhen starting Apache, so the pre-push check reads directly from/usr/src/wordpress, where the image stores its source files. -
Pull and tag version 2
Repeat with a different WordPress version:
Terminal window docker pull wordpress:6.5.5docker run --rm wordpress:6.5.5 grep wp_version /usr/src/wordpress/wp-includes/version.phpdocker tag wordpress:6.5.5 <your-repo-uri>:wp-6.5-v2docker push <your-repo-uri>:wp-6.5-v2 -
Verify both images are in ECR
Terminal window aws ecr describe-images --repository-name cs312-wordpress-lab \--query 'imageDetails[*].[imageTags,imagePushedAt,imageDigest]' \--output tableYou should see two rows, one for each tag. Note the
imageDigest(SHA256 hash) for your v1 image; this is the immutable identifier that guarantees you are running exactly the image you think you are.
Version Switching and Rollback
Section titled “Version Switching and Rollback”-
Create a Compose file using ECR images
If you still have a
docker-compose.ymlfrom Lab 3, update thewordpressservice image. Otherwise, create a new one:Terminal window mkdir ~/ecr-lab && cd ~/ecr-labCreate a
.envfile with your database credentials (same as Lab 3), then createdocker-compose.yml:services:db:image: mariadb:11restart: unless-stoppedenv_file: .envvolumes:- db_data:/var/lib/mysqlwordpress:image: <your-repo-uri>:wp-6.4-v1restart: unless-stoppedports:- "80:80"environment:WORDPRESS_DB_HOST: dbWORDPRESS_DB_USER: ${MYSQL_USER}WORDPRESS_DB_PASSWORD: ${MYSQL_PASSWORD}WORDPRESS_DB_NAME: ${MYSQL_DATABASE}WORDPRESS_CONFIG_EXTRA: |define('WP_AUTO_UPDATE_CORE', false);volumes:- wp_content:/var/www/html/wp-contentdepends_on:- dbvolumes:db_data:wp_content:The
WORDPRESS_CONFIG_EXTRAline disables WordPress’s built-in auto-update mechanism. Without it, WordPress silently upgrades itself to the latest version shortly after startup, which would defeat the purpose of pinning image versions. This is correct practice for containerized deployments: in the container model, you never update software inside a running container. Version changes happen by pulling a new image and redeploying, the approach you are practicing in this lab. (On a non-containerized WordPress server, you would leave minor and security auto-updates enabled.) -
Start the stack and create content
Terminal window docker compose up -dVisit
http://<your-public-ip>, complete WordPress setup, and create a blog post with your name and today’s date. This post is your test data.Then switch to a theme that works across both WordPress versions. In the WordPress admin (Appearance → Themes), activate Twenty Twenty-Four. This avoids a crash when rolling back to 6.4, because the Twenty Twenty-Five theme (bundled with 6.5) uses an API that does not exist in 6.4.
-
Switch to version 2
Edit
docker-compose.ymland change the WordPress image tag fromwp-6.4-v1towp-6.5-v2:Terminal window vim docker-compose.ymlThen pull the new image from ECR and apply the change:
Terminal window docker compose pulldocker compose up -d -VTwo flags matter here.
docker compose pullfetches the image from ECR rather than using whatever is cached locally. The-V(--renew-anon-volumes) flag tells Compose to discard the anonymous volume at/var/www/htmlthat the WordPress image creates behind the scenes. Without-V, Docker copies the old container’s anonymous volume data into the new container, and the WordPress entrypoint sees the old version’s files already in place and skips the update. Your named volumes (db_dataandwp_content) are unaffected by-V, so your blog post and database survive the switch.Verify the WordPress version inside the container:
Terminal window docker exec $(docker compose ps -q wordpress) \cat /var/www/html/wp-includes/version.php | grep wp_version -
Roll back to version 1
Change the image tag back to
wp-6.4-v1in your Compose file and run:Terminal window docker compose pulldocker compose up -d -VVerify the version again with the same
docker execcommand. You have just performed a rollback, a critical skill for recovering from a bad deployment.
Backing Up to S3
Section titled “Backing Up to S3”Amazon S3 is an object storage service designed for durability (99.999999999%, often called “11 nines”). It is the standard destination for backups in AWS.
-
Create an S3 bucket
Bucket names must be globally unique across all AWS accounts. Choose a name that includes your username or student ID:
Terminal window aws s3 mb s3://cs312-<your-username>-backups --region us-east-1 -
Dump the database
Use
mysqldumpto export the WordPress database from inside the MariaDB container:Terminal window docker exec $(docker compose ps -q db) \mariadb-dump -u root -p"$(grep MYSQL_ROOT_PASSWORD .env | cut -d= -f2)" wordpress > backup.sqlThis creates a SQL file on your EC2 instance containing every table, row, and setting in the WordPress database.
-
Upload to S3
Terminal window aws s3 cp backup.sql s3://cs312-<your-username>-backups/backups/Verify it arrived:
Terminal window aws s3 ls s3://cs312-<your-username>-backups/backups/ -
Configure a lifecycle rule
Lifecycle rules automate data management. You will create a rule that automatically deletes backups older than 7 days to prevent storage costs from growing indefinitely:
Terminal window aws s3api put-bucket-lifecycle-configuration \--bucket cs312-<your-username>-backups \--lifecycle-configuration '{"Rules": [{"ID": "expire-old-backups","Prefix": "backups/","Status": "Enabled","Expiration": { "Days": 7 }}]}'In production, you would choose a retention period based on your Recovery Point Objective (RPO): how much data loss is acceptable. Seven days is reasonable for a lab; a financial application might keep backups for years.
Restoring from Backup
Section titled “Restoring from Backup”A backup you have never tested is not a backup; it is a hope. This section proves your backup actually works.
-
Destroy the database volume
Terminal window docker compose down -vThis deletes all volumes, including your database. Your blog post is gone from the running system.
-
Start fresh containers
Terminal window docker compose up -dWait about 15 seconds for MariaDB to initialize the empty database.
-
Download the backup from S3
Terminal window aws s3 cp s3://cs312-<your-username>-backups/backups/backup.sql ./restore.sql -
Import the backup
Terminal window docker exec -i $(docker compose ps -q db) \mariadb -u root -p"$(grep MYSQL_ROOT_PASSWORD .env | cut -d= -f2)" wordpress < restore.sql -
Verify the restore
Visit
http://<your-public-ip>. Your WordPress site should be back, including your blog post with your name and date. This proves your backup and restore pipeline works end to end.
Clean Up
Section titled “Clean Up”When you are done:
docker compose downDelete the ECR repository and S3 bucket as well to avoid charges:
aws ecr delete-repository --repository-name cs312-wordpress-lab --force --region us-east-1aws s3 rb s3://cs312-<your-username>-backups --forceYou now know how to use a private container registry, manage image versions with meaningful tags, perform version switches and rollbacks, and implement a backup/restore pipeline with S3. These are the operational building blocks you will automate with Terraform and Ansible in the next labs.