HealthTech - HIPAA
For a telehealth company, EHR vendor, health data platform or any team about to handle protected health information (PHI) for the first time. The kit deploys from the hipaa.compliance.tf registry host and builds in the decisions that take weeks to retrofit: RDS encryption at creation, S3 Object Lock for retention and PHI network isolation. When you finish, the stack is applied and every HIPAA control its modules enforce has passed terraform plan.
Repository: compliancetf/starter-kit-healthtech-hipaa · About 140 resources
Before you start
- The shared prerequisites on Compliance starter kits: an account whose plan includes HIPAA, Terraform at a version every module accepts, AWS credentials, a Route 53 hosted zone, the replication role and destination bucket, and account-level CloudTrail. The replication pair supports the §164.308(a)(7)(ii)(A) data backup plan, and §164.312(b) audit controls expect the CloudTrail trail alongside the resource logs this kit configures.
- A signed AWS Business Associate Agreement (BAA), from the AWS Artifact console, before any PHI reaches the account. See Business associate agreements.
- Leave RDS encryption on.
storage_encrypted = truehas to be set when the database is first created; AWS cannot encrypt an existing instance without a snapshot and restore to a new one, which means migrating data, changing connection strings and rebuilding what depends on it. The kit enforces encryption by default. - Decide on one database. The kit provisions a standard RDS PostgreSQL instance and an Aurora PostgreSQL cluster; most teams need one. Delete the module block you do not use, and its output in
outputs.tf, before the first apply.
Deploy
git clone https://github.com/compliancetf/starter-kit-healthtech-hipaa.git
cd starter-kit-healthtech-hipaa
cp terraform.tfvars.example terraform.tfvars
# Set project_name, domain_name, route53_zone_id, availability_zones,
# s3_replication_role_arn and s3_replication_destination_bucket_arn
terraform login hipaa.compliance.tf
terraform init
terraform plan
terraform applyIf a value in terraform.tfvars breaks a HIPAA control, terraform plan fails with an error that names the control and the module. Fix the value and plan again.
What it deploys

PHI data stores sit in private subnets with no public routes. API Gateway and the ALB take authorized ingress. EKS and Lambda process PHI inside the VPC. OpenSearch holds audit logs for search and retention. S3 Object Lock holds PHI for the retention period.
| Module | Version | Purpose | HIPAA Security Rule requirements |
|---|---|---|---|
vpc | ~> 6.0 | PHI network isolation, flow logs | §164.312(a)(1) (access control), §164.312(b) (audit controls) |
alb | ~> 10.0 | HTTPS load balancing, access logs | §164.312(e)(1) (transmission security), §164.312(b) (access logs) |
apigateway | ~> 6.0 | API Gateway with PHI route access logging | §164.312(b) (audit controls) |
waf | ~> 1.0 | Web Application Firewall | §164.312(e)(1) (guard against unauthorized access to ePHI in transit) |
acm | ~> 6.0 | TLS certificate | §164.312(e)(1) (transmission security) |
kms | ~> 4.0 | Customer-managed key wired to EKS, Lambda, RDS, EFS, and OpenSearch | §164.312(a)(2)(iv) (encryption and decryption) |
lambda | ~> 8.0 | VPC-attached PHI processing, KMS encryption | §164.312(a)(1) (restrict access to authorized programs) |
ec2_instance | ~> 6.0 | Private worker nodes, IMDSv2, IAM instance profile | §164.312(a)(1) (access control) |
eks | ~> 21.0 | Kubernetes cluster, private endpoint, encrypted secrets | §164.312(a)(2)(iv) (encryption), §164.312(b) (audit controls) |
rds | ~> 7.0 | Standard RDS PostgreSQL, encrypted at creation | §164.312(a)(2)(iv) (critical: encryption must be set at creation) |
rds_aurora | ~> 10.0 | Aurora PostgreSQL, Multi-AZ, automated backups | §164.312(c)(1) (integrity), §164.308(a)(7)(ii)(A) (data backup plan) |
dynamodb | ~> 5.0 | Key-value store, KMS encryption, point-in-time recovery | §164.312(a)(2)(iv) (encryption), §164.312(c)(1) (integrity) |
s3_bucket_phi | ~> 5.0 | PHI storage, Object Lock, 7-year retention, cross-region replication | §164.312(c)(1) (integrity), §164.308(a)(7)(ii)(A) (data backup plan) |
s3_bucket_logs | ~> 5.0 | Audit log storage, versioning, cross-region replication, 365-day lifecycle | §164.312(b) (audit controls), §164.308(a)(7)(ii)(A) (data backup plan) |
efs | ~> 2.0 | Encrypted file storage, KMS encryption | §164.312(a)(2)(iv) (encryption and decryption) |
sqs | ~> 5.0 | Message queue with dead-letter queue | §164.312(a)(2)(iv) (encryption) |
elasticache | ~> 1.0 | Redis, encrypted at rest and in transit, Redis AUTH | §164.312(d) (authentication), §164.312(e)(1) (transmission security) |
opensearch | ~> 2.0 | VPC-only search, KMS encryption, HTTPS, fine-grained access, audit logs | §164.312(a)(1) (access control), §164.312(b) (audit controls) |
cloudwatch | ~> 5.0 | Application log group, 365-day retention | §164.312(b) (audit controls) |
Aurora (a writer and a reader) is the primary PHI store. The standard instance is sized for lower-tier PHI at roughly a seventh of the cost; compare the two rows under Cost. Neither module references the other.
Control coverage
| HIPAA requirement | Description | Modules | What the kit configures |
|---|---|---|---|
| §164.312(a)(1) | Access Control | RDS, S3, EFS, OpenSearch, Lambda | IAM authentication, no public access, VPC isolation |
| §164.312(a)(2)(iv) | Encryption and Decryption | All data modules, KMS | KMS encryption at rest for all PHI data stores |
| §164.312(c)(1) | Integrity | S3, DynamoDB, RDS Aurora | Object lock, versioning, point-in-time recovery |
| §164.312(d) | Person or Entity Authentication | RDS, ElastiCache, OpenSearch | IAM auth, Redis AUTH, fine-grained access control |
| §164.312(e)(1) | Transmission Security | ALB, ElastiCache, OpenSearch | TLS 1.2+ enforced on all connections |
| §164.312(e)(2)(ii) | Encryption in Transit | All network modules | HTTPS/TLS required throughout |
| §164.308(a)(7)(ii)(A) | Data Backup Plan | RDS, RDS Aurora, S3 PHI | Automated backups, PITR, cross-region replication |
| §164.312(b) | Audit Controls | All modules | VPC flow logs, API Gateway access logs, CloudWatch log groups |
Settings that a module enforces as a control fail the plan if you change them. The rest are kit defaults you can change. The HIPAA framework page lists the controls modules enforce and the mapped-only controls that stay with your team.
What you own
The kit configures the infrastructure settings above. These stay with you and your privacy officer:
- Business Associate Agreements with AWS and every third-party vendor that touches PHI
- Application-level PHI access controls and audit logging at the field level
- Security awareness training (§164.308(a)(5)) and information access management (§164.308(a)(4))
- Security incident response procedures (§164.308(a)(6))
- Breach notification to affected individuals, HHS, and media when required (45 CFR §164.400-414)
- Risk analysis and risk management (§164.308(a)(1)(ii)(A)-(B))
- Physical device safeguards for workstations and portable media
- Privacy Rule compliance and patient rights management
PHI retention
HIPAA's 6-year retention requirement (45 CFR §164.316(b)(2)(i)) applies to Security Rule documentation (policies, procedures, risk analyses), not to clinical PHI records. State law sets clinical record retention: most states require 5 to 10 years, and some longer for pediatric records.
The kit defaults to 7 years, which covers the federal documentation minimum and most state requirements. Set it in terraform.tfvars:
phi_retention_years = 7 # adjust to match your jurisdictionThe s3_bucket_phi module holds objects with S3 Object Lock in GOVERNANCE mode: an object cannot be deleted before its retention period ends unless the caller has s3:BypassGovernanceRetention. COMPLIANCE mode, set in main.tf, locks out the AWS root user too, with no override; use it only when you are certain of the retention period.
HIPAA-eligible AWS services
Every service in the kit is on the AWS HIPAA-eligible services list. Only services covered by the AWS BAA may store or process PHI, and the list changes, so check it before adding a service. PHI routed through an ineligible service falls outside the BAA.
Business associate agreements
Compliance.tf does not manage BAAs. You are responsible for:
- Signing an AWS BAA before any PHI touches AWS infrastructure (done in the AWS Artifact console)
- BAAs with any third-party vendor that receives or processes PHI (analytics, monitoring, CDN, support tooling)
- BAAs with subcontractors who have access to systems that handle PHI
The AWS BAA covers the services in this kit at the infrastructure level. Application-level PHI handling, access controls, and workforce training are your responsibility under the HIPAA Privacy Rule and administrative safeguards.
Customize
Turn off a control
Use the HTTPS source with ?disable=:
module "example" {
source = "https://hipaa.compliance.tf/terraform-aws-modules/<module>/aws?version=<version>&disable=<control_id>"
# ...
}Two things decide whether this does anything:
- Only a control the module enforces can be turned off. Disabling a control the framework maps but the module does not enforce does nothing, and nothing tells you so. Each control page says whether it is enforced.
- Inside the PHI boundary, leave them on. Do not turn off an encryption, Object Lock, backup or audit logging control on a module that stores or processes PHI. An auditor will ask why it is absent, and "it did not apply to our use case" does not survive the question. The parameter is for modules outside the PHI boundary, such as a cost or observability setting on a workload that stores no patient data.
Turn controls on and off explains the limits and how to record the reason.
Adjust sizes or remove a module
Instance sizes and counts are variables; override them in terraform.tfvars, with the names in the repository's variables.tf. To drop a module the application does not use, delete its block from main.tf and its outputs from outputs.tf, then check that no other module references it.
Cost
| Component | Dev | Prod |
|---|---|---|
| VPC + NAT Gateway | ~$35 | ~$100 |
| ALB | ~$25 | ~$25+ |
| EKS control plane | ~$75 | ~$75 |
| EKS nodes (t3.medium) | ~$60 | ~$120+ |
| RDS PostgreSQL (db.t3.medium) | ~$30 | ~$60 |
| Aurora PostgreSQL (db.r6g.large) | ~$200 | ~$400 |
| ElastiCache (cache.t4g.micro) | ~$10 | ~$20 |
| OpenSearch (t3.small.search) | ~$20 | ~$60 |
| EFS | ~$5 | ~$10+ |
| S3 PHI (with Object Lock) | ~$5 | ~$10+ |
| Lambda + SQS + EC2 | ~$30 | ~$60+ |
| Total of the rows | ~$495 | ~$940+ |
The total is the sum of the rows above, with both databases; deleting one of them lowers it. The table has no rows for API Gateway, WAF, DynamoDB, KMS or CloudWatch Logs, so add those. Usage charges come on top: data transfer, NAT gateway data processing, log ingestion, and KMS and WAF requests. Actual costs depend on usage, data transfer and region. Infracost estimates them from your own terraform plan.