Imageboard
A microservices platform across four repositories โ Kubernetes, Terraform, S3, SQS
Why I built it
I wanted to go beyond single-service Flask apps and learn how real cloud-native systems are structured. That meant infrastructure as code, container orchestration, event-driven architecture, and a proper separation between services, things you can't learn from a tutorial project. Building something with four moving parts that all had to work together forced me to understand each layer.
Architecture
The project is split across four repositories, each owning its own vertical: application code, container spec, Kubernetes manifests, and Terraform resource definitions. A platform repository sits above them all, providing shared Terraform modules for S3, SQS, and RDS, plus a reusable GitHub Actions CI/CD pipeline that the product repos invoke directly.
The backend API is stateless by design, identity rides in a signed cookie, durable data lives in PostgreSQL, images in S3, and slow work (image uploads) goes on an SQS queue so the API returns immediately. A separate SQL worker service consumes that queue and writes to the database. Reads never go on the queue but only writes that can tolerate a short delay.
Locally, the whole system runs with kind (Kubernetes in Docker) and LocalStack
emulating S3, SQS, and RDS; no AWS account needed. make up
brings the entire cluster up; make down tears it down.
Moving to AWS requires initialising the Terraform infrastructure (~15 minutes).
What I learned
Writing Terraform modules that work against both LocalStack and real AWS taught me how infrastructure abstraction actually works. The same module creates an S3 bucket locally or in us-east-1 depending on the environment variable. That separation made it possible to develop and test everything for free before touching real cloud resources.
The platform governance model was the most interesting design decision: product teams request infrastructure by calling the shared Terraform modules, but they never modify the modules themselves. The goal is that a backend team can ship a feature needing a new queue with no one from the platform team in the critical path. Setting up that boundary made me think about platform engineering as a product in itself.
Event-driven architecture with SQS also changed how I think about latency. The upload endpoint returns instantly and the SQL worker handles the slow part asynchronously. That pattern; put slow work on a queue, keep reads synchronous is something I now think about when designing any API.