A Kotlin/Spring Boot REST API for tracking captured Pokemon, deployed to AWS (EC2, RDS) with Terraform and Docker.
- API: http://54.209.33.157/pokemon
- Swagger UI: http://54.209.33.157/swagger-ui/index.html
Deployed on the EC2 setup described below and left running (see Deploying to AWS) rather than torn down between visits, so the link should work whenever you click it.
./gradlew bootRun
Requires a local Postgres reachable at localhost:5432/appdb (see src/main/resources/application.properties)
and a DB_PASSWORD environment variable.
Infrastructure lives under infra/, one subdirectory per deployment target so alternate approaches can
coexist without colliding. This one, infra/ec2/, is deliberately minimal/cheap:
- EC2 (
t3.micro, default VPC) runs the API container via Docker Compose. Managed through SSM Session Manager — there's no open SSH port. - RDS Postgres (
db.t3.micro, single-AZ, 20GB) — free-tier sized, not publicly accessible, reachable only from the EC2 instance's security group. - ECR holds the built API image.
- The DB password is generated by Terraform, stored in SSM Parameter Store (SecureString), and read by the instance at deploy time via its IAM role — never embedded in code or committed anywhere.
- An Elastic IP keeps the public address stable across redeploys/instance replacement, so the link above doesn't break.
This is intentionally not a "real" production setup (single instance, no HTTPS/custom domain, no autoscaling, no remote Terraform state) — see Possible next steps.
terraform,awsCLI, anddockerinstalled locally- AWS credentials configured (
aws sts get-caller-identityshould work)
cd infra/ec2
terraform init
terraform plan # review what will be created
terraform apply
cd ..
./scripts/build_and_push.sh # build the image and push to ECR
./scripts/redeploy.sh # tell the running instance to pull it and restartterraform apply provisions the EC2 instance before an image exists in ECR, so the instance's first boot has
nothing to pull — that's expected. build_and_push.sh + redeploy.sh complete the deploy.
The API's public URL is printed as the api_url Terraform output:
terraform -chdir=infra/ec2 output api_url
curl "$(terraform -chdir=infra/ec2 output -raw api_url)/pokemon"./scripts/build_and_push.sh
./scripts/redeploy.shcd infra/ec2
terraform destroyEverything (EC2, RDS, ECR repo + images, SSM parameter, IAM role, Elastic IP) is destroyable in one step — nothing has deletion protection or a final-snapshot requirement. The demo above is intentionally left running for now; only tear it down once you're done using it as a live reference.
- Remote Terraform state (S3 + DynamoDB lock) instead of local state
- Custom domain + HTTPS: point a subdomain (e.g.
pokemon-api.davenotdavid.com) at the Elastic IP and run Caddy as a reverse proxy on the instance for automatic Let's Encrypt certs — cheaper than an ALB + ACM for a single-instance setup - Continuous deployment: extend CI to run
build_and_push.sh/redeploy.shautomatically on push tomain - Multi-AZ RDS + automated backups once this is more than a demo