Built and documented by Ali Adil.
CloudPress is my AWS setup for a WordPress business website. I built it for Cedar & Tide Studio, a fictional interior-design business in British Columbia. The website shows its services and story; WordPress provides the tools to manage its content.
Start here: Every service and connection explained in easy words · Open the website preview
I used this small-business scenario: the whole website depends on one server, which is a rented computer. If it fails, customers cannot see the site. If photos are saved only on that computer, replacing it can lose the photos. Updating the site by hand also makes mistakes harder to track and recover from.
I wanted a setup that could share the work between two servers, keep the website's data separate, restrict access to passwords and the database, and follow the same checked steps for each release. This is a learning project for a fictional business, not a claim about a real client's website.
- A visitor opens the website through CloudFront, its public entry point with an encrypted browser connection.
- A load balancer chooses a healthy website server to answer the request.
- One of two EC2 servers runs WordPress and builds the page.
- RDS, the database, stores pages, users and settings. EFS, shared file storage, gives both servers access to the same upload folder.
- When I release a change, automated checks run first. The deployment then updates one server at a time and checks it before returning it to service.
- CloudWatch collects logs and measurements so I can inspect errors and the health of the system.
I wrote the AWS setup in Terraform, which is a set of files describing what AWS should create. That makes the setup repeatable and gives me a way to remove the temporary environment after testing.
This design reduces dependence on one website server. The short lab still used one database location and one shared outbound connection, so I do not claim that every part could survive a location failure. The service guide explains these limits without requiring AWS experience.
Where the parts sit: CloudFront is outside my VPC, the project's network. The load balancer is in public network sections. The website servers and database use private network sections. A visitor reaches the private servers through the load balancer; the visitor does not connect directly to the database. The connection walkthrough shows this with a diagram.
I deployed the WordPress application on AWS. Both website servers were healthy, all five release stages succeeded, and the homepage, services page, story page and health check passed. I did not run the separate server-failure, traffic-growth or backup-restore exercises. Test results and remaining exercises.
My account could not use CodeDeploy, the first deployment service I tried. I used Systems Manager to deliver the releases instead. The build journal records that problem and the other fixes.
I kept the lab short to control costs, then removed its AWS resources and checked the cleanup. The website preview remains on GitHub Pages. It shows the design and project story; it does not run WordPress or connect to a live AWS database. Cleanup evidence · My cost report.
- Service guide: what every service did, why I used it, and an example request from start to finish.
- Build journal: the problems I encountered and how I fixed them.
- Testing: what I actually checked and what I have not tested.
- Operations manual: the detailed steps for creating, updating and removing the AWS environment.
The sections below keep the technical settings and commands for someone who wants to rebuild the project.
flowchart TB
visitor[Visitor in British Columbia] -->|HTTPS| cf[CloudFront\nPresentation and cache policy]
github[GitHub main branch] --> connection[CodeConnections]
connection --> pipeline[CodePipeline\nBuild · initialize · deploy · verify]
subgraph vpc[CloudPress VPC · us-east-1 · two Availability Zones]
subgraph public[Public subnets]
alb[Application Load Balancer\nCloudFront ranges + origin header]
nat[NAT Gateway\none in lab / one per AZ in production]
end
subgraph application[Private application subnets]
asg[EC2 Auto Scaling\nNginx + PHP-FPM + WordPress]
init[Short-lived private CodeBuild\ndatabase initialization]
efs[EFS regional filesystem\nshared uploads / TLS + IAM]
end
subgraph data[Private database subnets · no default internet route]
rds[RDS MySQL 8.4\nprivate + encrypted + TLS required]
end
alb -->|HTTP inside VPC| asg
asg -->|TLS / app DB user| rds
asg -->|NFS over TLS| efs
init -->|TLS / bootstrap DB admin| rds
asg -->|outbound HTTPS| nat
init -->|outbound HTTPS| nat
end
cf -->|HTTPS with custom domain / lab HTTP| alb
pipeline --> init
pipeline --> deploy[CodeBuild + Systems Manager\none instance at a time\noptional CodeDeploy]
deploy --> asg
secrets[Secrets Manager\nseparate master · app · initial admin] --> init
secrets -->|app secret only| asg
asg --> watch[CloudWatch logs, metrics, alarms]
rds --> watch
The three tiers are presentation/web, application, and database. CloudFront/ALB handle public delivery and traffic distribution; PHP/WordPress implement application behavior; RDS stores persistent relational content. Nginx and PHP are deliberately colocated on each application host. This is a logical three-tier design, not a claim that every web/PHP process has a separate EC2 fleet. Detailed flows and failure boundaries.
| Setting | Short-lived lab | production_mode=true |
|---|---|---|
| Initial app instances | 2; SSM ASG range 2–3 | 2; ASG range 2–4 |
| Database | Single-AZ, 1-day backups | Multi-AZ, 7-day backups |
| Outbound networking | One NAT shared across AZs | NAT in each AZ |
| Viewer URL | Default CloudFront HTTPS hostname | Required custom hostname/certificate |
| CloudFront → ALB | HTTP, restricted source ranges and header | HTTPS, certificate validated |
| ALB → application | Private HTTP | Private HTTP; see TLS boundary discussion |
| Deployment approval | Automated after successful checks | Manual production approval after build |
| Deletion protection | Disposable lab settings | RDS and ALB protection, final DB snapshot |
| Logs | 7 days | 30 days |
Production mode enables specific infrastructure controls. It is not a claim of a fully audited production service or a guarantee of availability. Security controls and remaining limitations.
The default enable_deployment=false creates no platform resources. Enabling it provisions the complete billable environment, rather than leaving an incomplete zero-instance demonstration.
Prerequisites:
- Existing working non-root AWS access and a verified account remaining on its Free plan.
- Terraform 1.10 or later; the recorded validation used 1.15.9.
- A GitHub repository and an AVAILABLE CodeConnections connection with access to that repository.
- A private input file with
github_repository,connection_arn,expires_at, and explicit deployment opt-in. - For production: an existing public Route 53 zone and the site's hostname. No domain purchase is automated.
terraform -chdir=terraform init
terraform -chdir=terraform plan -var-file=/private/cloudpress.tfvars -out=/private/cloudpress.tfplan
terraform -chdir=terraform apply /private/cloudpress.tfplan
terraform -chdir=terraform output -json platformUse one protected state per environment. The repository includes a backend configuration example; it does not create or take ownership of an unrelated shared state bucket. The local state and plan files must remain private because the origin access header is stored in state. Database/admin passwords never pass through Terraform.
The first pipeline build downloads the pinned WordPress release, verifies its published core checksums, adds the original theme and platform plugin, and packages the release. A separate private CodeBuild stage creates the database-scoped application user and seeds the business website under a database lock. The selected delivery driver installs the same release on the ASG instances, validates dependencies, and controls ALB traffic. The default SSM driver verifies an immutable SHA256 archive, drains one host at a time, restores failed releases, and registers only locally healthy hosts. The last stage invalidates old CDN objects and checks the public website.
Full prerequisites, rollout, administrator access, local checks, troubleshooting, rollback, monitoring, and cleanup are in the operations manual.
| Path | Purpose |
|---|---|
app/theme/ |
Original accessible Cedar & Tide WordPress theme; no third-party images or branding |
app/mu-plugins/ |
Health endpoint and platform safeguards |
app/wp-config.php |
Runtime-only secret/config loading and CMS restrictions |
config/ |
Pinned application/tool versions and fictional business content |
terraform/ |
Environment entry point and one cohesive reusable platform module, divided by responsibility |
ops/ |
Initialization, atomic secret refresh, release verification/rollback, expiry guard, smoke checks |
deploy/ |
Shared host lifecycle hooks, also usable by optional CodeDeploy |
scripts/ |
Artifact build and intentional Terraform teardown helper |
tests/ |
Credential handling, expiry, SQL scope/lock, release integrity, rollback, and response-contract tests |
buildspec*.yml, appspec.yml |
The actual AWS-native delivery path |
.github/workflows/quality.yml |
Additional pull-request checks; does not replace CodePipeline or hold AWS credentials |
The live GitHub Pages preview preserves the original theme and links to this case study after the AWS lab. Its publication and browser check succeeded. It is explicitly a static portfolio preview with no active WordPress backend. Build and evidence details. The AWS pipeline remains the real application delivery path.
python -m unittest discover -s tests -v
python -m ops.security_scan
terraform -chdir=terraform fmt -check -recursive
terraform -chdir=terraform init -backend=false
terraform -chdir=terraform validateI ran 40 local cases: 39 passed and one Windows symlink integration test was skipped. The Linux AWS quality build also passed. I then verified the real WordPress initialization, rolling deployment to two private servers, and all four public smoke-test paths. Every CodePipeline stage succeeded; both ALB targets were healthy. The journal explains the AWS CLI input failure and CloudFront hostname redirect I diagnosed and fixed. Live AWS results · Test scope and remaining exercises.
- Security and IAM: trust boundaries, secret lifecycle, ingress/egress, privilege limits, and production gaps.
- Cost and cleanup: approximately $0.40–1.50 planned for a two-hour lab, a $2 troubleshooting allowance for three hours, and how to remove every project workload. I set a one-time $100 credit budget for all three projects and kept the demonstrations short.
- Architecture decisions: why EC2/ASG, RDS, EFS, NAT, AWS-native delivery, and this region were chosen.
- Build journal: actual difficulties, the account-plan blocker, corrections, and remaining evidence.
- Interview preparation: concise explanations and technical questions with defensible answers.
- Screenshot checklist: real captures only; no fabricated infrastructure or performance results.
The website intentionally uses fictional content and does not collect enquiries. This project favors a coherent operating system over a large service count. No measured performance, savings, production user count, or completed deployment claim should be inferred from the architecture alone.
I consolidated my planning choices, AWS-reported charges and credit balance in my cost report. I keep estimates separate from posted billing and verify teardown independently.