Procal
From ClickOps to GitOps: Terraform, Atlantis, and Jenkins for a 4-service cost-estimate and approval platform.
Procal is an internal tool for building, submitting, and approving project cost estimates. Three roles (admin, PM, and approver) move an estimate through a Draft → Submitted → Approved / Rejected state machine, with every transition recorded in an immutable audit log. It was built and is operated by a small cohort team as part of the DevOps Institute program. The application itself is four services, but the real focus of the project was the infrastructure underneath it: moving from ClickOps and manual SSH deploys to a full Terraform + Atlantis GitOps workflow.
Application architecture
diego (the iOS approver app) never calls dora directly; it goes through maps' API proxy route, same as the web client.
Infrastructure: layered Terraform stacks
One shared VPC across all environments, S3-backed Terraform state, applied through Atlantis GitOps. Direct local terraform apply is reserved for the foundation layer only.
Tech stack
Application
Infrastructure
CI/CD
Config-as-Code
Security & Lint
Observability
GitOps & deployment flow
- 1
Open a PR
A change to any Terraform stack is proposed via pull request.
- 2
Atlantis auto-plans
Atlantis detects the affected project(s) and runs a plan automatically; no manual trigger needed.
- 3
tflint + Checkov gate the plan
Both scanners run on every plan. Skipped checks (e.g. RDS Multi-AZ on throwaway learner environments) are documented inline with a reason, not silently ignored.
- 4
Team reviews the plan output
The plan diff is posted straight to the PR for review before anything touches real infrastructure.
- 5
`atlantis apply` on approval
Applying is a deliberate action: someone comments `atlantis apply -p <project>` once the plan looks right.
- 6
Jenkins handles the rest
Ansible-lint runs on every push; app deploys go out via Ansible (dev) or a bake-AMI → SSM → Terraform ASG refresh path (staging/prod); rollback redeploys a prior AMI.
Highlights
- Terraform stacks are strictly dependency-layered (foundation → bootstrap → shared → per-env commons → per-env services), with apply/destroy order documented so nobody has to guess what depends on what.
- Every plan runs through two independent static-analysis tools before a human even looks at it.
- Infra changes are reviewed as code, in a PR, before they're applied, a deliberate move away from ad-hoc console changes and manual SSH deploys.
- Staging and prod use an AMI-bake + Auto Scaling Group instance-refresh deploy model instead of direct SSH, so a rollback is just redeploying a known-good AMI.
- Each backend service ships with its own Grafana dashboard (infra, application, and log panels), not a single generic one.