Training management SaaS across four AWS environments

Technologies
Summary
A training management SaaS with a Vue.js frontend, a Ruby on Rails backend and PostgreSQL on RDS, deployed to four AWS environments.
This is a training management SaaS with a Vue.js frontend, a Ruby on Rails backend and PostgreSQL on RDS. It runs in four environments, dev, release, demo and prod, and each one has its own AWS credentials, ECR tag and EC2 host. Brakeman security scans and RuboCop linting must pass before an image is built. GitHub Actions builds the Docker images, pushes them to ECR and pulls them onto EC2. Each instance runs two containers, the Rails app behind Nginx and a Solid Queue worker, with separate pipelines. After a deploy, schema migrations, data migrations and seeds run inside the new container. The app server sits in a public subnet and accepts only HTTPS on 443, while RDS PostgreSQL sits in a private subnet. The Vue.js frontend is served from Cloudflare Pages and Workers, and bulk email goes through Postal.
Details — Tech Stack
14 technologies across 5 layers
frontend
backend
database
cloud
tools
Details — Key Features
Four environments (dev, release, demo, prod), each with its own AWS credentials, ECR tag and EC2 host
Brakeman security scan and RuboCop lint must pass before an image is built
Docker images built in GitHub Actions, pushed to ECR and pulled onto EC2
Two containers per instance, the Rails app behind Nginx and a Solid Queue worker, with separate pipelines
Schema migrations, data migrations and seeds run inside the new container after deploy
Pipeline fails and prints the container logs if the new container is not running
App server in a public subnet on HTTPS 443 only, with RDS PostgreSQL in a private subnet
Vue.js frontend on Cloudflare Pages and Workers, with DNS on Cloudflare and bulk email via Postal
Methodology
01
The database had to stay off the public internet while the app server stayed reachable for users.
The app server runs in a public subnet that accepts only HTTPS on 443. RDS PostgreSQL runs in a private subnet and accepts connections on 5432 only from the app's security group.
02
A broken or insecure build could reach an environment without anyone noticing.
Brakeman and RuboCop must pass before an image is built. After deploy, the pipeline checks that the new container is running, and if it is not, the job fails and prints the container logs.
Gallery
