↓ Skip to main content

Project 02: static site on AWS with Terraform and GitLab CI

3 mins

Problem
#

Publish a static site on AWS without storing access keys in the pipeline and without opening SSH to the GitLab runners. The application is the HTML and CSS course site by Gustavo Guanabara; the focus of the project is the infrastructure and the pipeline, not the site.

View the repository on GitLab

Project architecture
#

Arquitetura do projetoDeveloperGitLab CIIAM · role via OIDCAWS · us-east-1ECRecr-site-dandroidS3Terraform stateSSM Run Commanddeploy without SSHEC2 t3.microAmazon Linux 2023nginx container · 8080 → 80BrowserSSH tunnel (port 22)
Conceptual diagram based on the implementation documented in the GitLab repository.
  • Terraform creates a t3.micro EC2 instance in a new subnet, inside an existing VPC. The security group allows only SSH, from one IP address.
  • ECR stores the ecr-site-dandroid image, built from a Dockerfile based on nginx:alpine.
  • The Terraform state lives in an S3 bucket, with native locking.

Technical decisions and reasoning
#

  • SSM deploy instead of SSH from the pipeline: the shared GitLab runners have variable IPs. Opening port 22 for them would require exposing it to the internet. With SSM, the deploy uses the AWS API and no inbound port is needed.
  • OIDC instead of an access key: GitLab assumes a role through OIDC and receives temporary credentials. No long-lived key is stored.
  • Access only through an SSH tunnel: port 8080 on the EC2 instance is not published to the internet. To view the site, you open an SSH tunnel. Domain and HTTPS are not configured.
  • Image tag from the commit SHA: ECR uses mutable tags. Using latest would let each build overwrite the previous one.
  • Saved plan, then apply: the plan job generates a tfplan, and the apply job applies that same file. What was reviewed is what gets applied.
  • Manual apply, on one branch only: apply is manual and configured for the mes-11-projects branch.

Real challenges
#

  • Dependency on the first run: the build job pushes the image to an ECR repository that only exists after apply. On the first run, build fails. The README advises setting allow_failure on that job for that run and removing it afterwards.

What I would do differently
#

  • HTTPS with a domain: today the site is only reachable through an SSH tunnel. With a domain and a certificate, on the host or behind an ALB with ACM, it could be public.
  • Docker in user_data: the deploy installs Docker on every run. Installing it in user_data would leave the instance ready when it starts.
  • Role and bucket as variables: the role name and the state bucket are hardcoded, which makes the project harder to reproduce in another account.
  • Least privilege on the role: the role creation policy uses Resource: "*". I would restrict it by ARN and by tag.
  • Apply from main: today apply only runs on the working branch. In a real workflow, it would run from main after review.

Technologies
#

Terraform, AWS (EC2, ECR, S3, IAM, SSM, VPC), Docker, nginx, GitLab CI, OIDC.