DevOps & CI/CD

O que Todo Dev Deve Saber sobre Terraform com AWS: VPC, EC2, RDS e IAM na Prática

21 min de leitura

O que Todo Dev Deve Saber sobre Terraform com AWS: VPC, EC2, RDS e IAM na Prática

Fundamentos do Terraform e AWS O Terraform é uma ferramenta de Infrastructure as Code (IaC) que permite descrever, versionizar e gerenciar toda a infraestrutura em nuvem através de arquivos de configuração declarativos. Diferente de scripts imperativos que dizem como fazer algo, o Terraform descreve o que você quer que exista, permitindo que a ferramenta figure out os passos necessários. Para trabalhar com AWS, você precisa autenticar o Terraform com suas credenciais AWS — isso pode ser feito através de variáveis de ambiente, arquivos de configuração local ou, em produção, através de roles IAM. A estrutura básica de um projeto Terraform consiste em arquivos que definem providers, recursos, variáveis e outputs. O provider é o intermediário entre seu código e a AWS — sem ele, o Terraform não sabe como se comunicar com a nuvem. Quando você executa , a ferramenta cria um arquivo que rastreia o estado atual da infraestrutura. Este arquivo é crítico e deve ser versionado com cuidado

<h2>Fundamentos do Terraform e AWS</h2>

<p>O Terraform é uma ferramenta de Infrastructure as Code (IaC) que permite descrever, versionizar e gerenciar toda a infraestrutura em nuvem através de arquivos de configuração declarativos. Diferente de scripts imperativos que dizem <em>como</em> fazer algo, o Terraform descreve <em>o que</em> você quer que exista, permitindo que a ferramenta figure out os passos necessários. Para trabalhar com AWS, você precisa autenticar o Terraform com suas credenciais AWS — isso pode ser feito através de variáveis de ambiente, arquivos de configuração local ou, em produção, através de roles IAM.</p>

<p>A estrutura básica de um projeto Terraform consiste em arquivos <code>.tf</code> que definem providers, recursos, variáveis e outputs. O provider é o intermediário entre seu código e a AWS — sem ele, o Terraform não sabe como se comunicar com a nuvem. Quando você executa <code>terraform apply</code>, a ferramenta cria um arquivo <code>terraform.tfstate</code> que rastreia o estado atual da infraestrutura. Este arquivo é crítico e deve ser versionado com cuidado em ambientes reais.</p>

<pre><code class="language-hcl"># provider.tf

terraform {

required_version = &quot;&gt;= 1.0&quot;

required_providers {

aws = {

source = &quot;hashicorp/aws&quot;

version = &quot;~&gt; 5.0&quot;

}

}

}

provider &quot;aws&quot; {

region = var.aws_region

}</code></pre>

<p>Antes de começar com VPC, EC2, RDS e IAM, você precisa ter uma conta AWS ativa e credenciais configuradas. Recomendo usar o AWS CLI para testar a conexão: <code>aws sts get-caller-identity</code>. Se retornar seus dados de account ID, você está pronto.</p>

<h2>VPC: Fundação de Toda Infraestrutura AWS</h2>

<p>A Virtual Private Cloud (VPC) é sua rede privada na AWS. Tudo o que você deploy — EC2, RDS, Lambda — precisa existir dentro de uma VPC. Uma VPC contém subnets (redes menores dentro da VPC), route tables (definem como o tráfego flui), internet gateways (permitem comunicação com a internet) e security groups (firewalls em nível de instância).</p>

<p>Quando você cria uma VPC, precisa definir um CIDR block — um intervalo de endereços IP privados. Por exemplo, <code>10.0.0.0/16</code> oferece 65.536 endereços IP. As subnets são divisões deste bloco: você pode ter <code>10.0.1.0/24</code> (256 IPs) como subnet pública e <code>10.0.2.0/24</code> como subnet privada. A subnet pública tem acesso à internet via internet gateway, enquanto a subnet privada não — perfeito para RDS que não precisa ser acessível da internet.</p>

<pre><code class="language-hcl"># vpc.tf

resource &quot;aws_vpc&quot; &quot;main&quot; {

cidr_block = var.vpc_cidr

enable_dns_hostnames = true

enable_dns_support = true

tags = {

Name = &quot;main-vpc&quot;

}

}

resource &quot;aws_subnet&quot; &quot;public&quot; {

vpc_id = aws_vpc.main.id

cidr_block = &quot;10.0.1.0/24&quot;

availability_zone = data.aws_availability_zones.available.names[0]

map_public_ip_on_launch = true

tags = {

Name = &quot;public-subnet&quot;

}

}

resource &quot;aws_subnet&quot; &quot;private&quot; {

vpc_id = aws_vpc.main.id

cidr_block = &quot;10.0.2.0/24&quot;

availability_zone = data.aws_availability_zones.available.names[1]

tags = {

Name = &quot;private-subnet&quot;

}

}

resource &quot;aws_internet_gateway&quot; &quot;main&quot; {

vpc_id = aws_vpc.main.id

tags = {

Name = &quot;main-igw&quot;

}

}

resource &quot;aws_route_table&quot; &quot;public&quot; {

vpc_id = aws_vpc.main.id

route {

cidr_block = &quot;0.0.0.0/0&quot;

gateway_id = aws_internet_gateway.main.id

}

tags = {

Name = &quot;public-rt&quot;

}

}

resource &quot;aws_route_table_association&quot; &quot;public&quot; {

subnet_id = aws_subnet.public.id

route_table_id = aws_route_table.public.id

}

data &quot;aws_availability_zones&quot; &quot;available&quot; {

state = &quot;available&quot;

}</code></pre>

<p>Neste código, criamos uma VPC com CIDR <code>10.0.0.0/16</code>, duas subnets em availability zones diferentes (essencial para alta disponibilidade), um internet gateway para acesso à internet e uma route table pública que roteia todo tráfego (0.0.0.0/0) para o internet gateway. A subnet pública tem <code>map_public_ip_on_launch = true</code>, o que significa que qualquer EC2 lançada nela recebe automaticamente um IP público.</p>

<h2>EC2 e Security Groups: Computação na Nuvem</h2>

<p>EC2 (Elastic Compute Cloud) são máquinas virtuais na AWS. Você especifica o tipo de instância (t2.micro, t3.small, m5.large, etc.), a AMI (Amazon Machine Image — basicamente o sistema operacional), e a quantidade de vCPU e memória que precisa. Security groups funcionam como firewalls — definem quais portas estão abertas e de onde o tráfego é permitido.</p>

<p>Ao criar uma EC2, você deve especificar a subnet (pública ou privada), atribuir uma role IAM (discutiremos depois), e configurar um security group. É extremamente importante não abrir a porta SSH (22) para <code>0.0.0.0/0</code> a menos que você tenha um motivo muito específico — isto é um risco de segurança grave. Em produção, use bastion hosts ou Session Manager da AWS.</p>

<pre><code class="language-hcl"># security_group.tf

resource &quot;aws_security_group&quot; &quot;web&quot; {

name = &quot;web-sg&quot;

description = &quot;Security group for web servers&quot;

vpc_id = aws_vpc.main.id

ingress {

from_port = 80

to_port = 80

protocol = &quot;tcp&quot;

cidr_blocks = [&quot;0.0.0.0/0&quot;]

}

ingress {

from_port = 443

to_port = 443

protocol = &quot;tcp&quot;

cidr_blocks = [&quot;0.0.0.0/0&quot;]

}

ingress {

from_port = 22

to_port = 22

protocol = &quot;tcp&quot;

security_groups = [aws_security_group.bastion.id]

}

egress {

from_port = 0

to_port = 0

protocol = &quot;-1&quot;

cidr_blocks = [&quot;0.0.0.0/0&quot;]

}

tags = {

Name = &quot;web-sg&quot;

}

}

resource &quot;aws_security_group&quot; &quot;rds&quot; {

name = &quot;rds-sg&quot;

description = &quot;Security group for RDS&quot;

vpc_id = aws_vpc.main.id

ingress {

from_port = 3306

to_port = 3306

protocol = &quot;tcp&quot;

security_groups = [aws_security_group.web.id]

}

egress {

from_port = 0

to_port = 0

protocol = &quot;-1&quot;

cidr_blocks = [&quot;0.0.0.0/0&quot;]

}

tags = {

Name = &quot;rds-sg&quot;

}

}

resource &quot;aws_security_group&quot; &quot;bastion&quot; {

name = &quot;bastion-sg&quot;

description = &quot;Security group for bastion host&quot;

vpc_id = aws_vpc.main.id

ingress {

from_port = 22

to_port = 22

protocol = &quot;tcp&quot;

cidr_blocks = [var.allowed_ssh_cidr]

}

egress {

from_port = 0

to_port = 0

protocol = &quot;-1&quot;

cidr_blocks = [&quot;0.0.0.0/0&quot;]

}

tags = {

Name = &quot;bastion-sg&quot;

}

}</code></pre>

<p>Agora vamos criar as instâncias EC2. A primeira é um bastion host (jump host) que você usa para acessar outras instâncias privadas. A segunda é um web server na subnet pública que será acessado via HTTP/HTTPS.</p>

<pre><code class="language-hcl"># ec2.tf

data &quot;aws_ami&quot; &quot;ubuntu&quot; {

most_recent = true

owners = [&quot;099720109477&quot;] # Canonical

filter {

name = &quot;name&quot;

values = [&quot;ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*&quot;]

}

filter {

name = &quot;virtualization-type&quot;

values = [&quot;hvm&quot;]

}

}

resource &quot;aws_instance&quot; &quot;bastion&quot; {

ami = data.aws_ami.ubuntu.id

instance_type = &quot;t2.micro&quot;

subnet_id = aws_subnet.public.id

vpc_security_group_ids = [aws_security_group.bastion.id]

iam_instance_profile = aws_iam_instance_profile.ec2_profile.name

key_name = aws_key_pair.deployer.key_name

tags = {

Name = &quot;bastion-host&quot;

}

}

resource &quot;aws_instance&quot; &quot;web&quot; {

ami = data.aws_ami.ubuntu.id

instance_type = &quot;t2.micro&quot;

subnet_id = aws_subnet.public.id

vpc_security_group_ids = [aws_security_group.web.id]

iam_instance_profile = aws_iam_instance_profile.ec2_profile.name

key_name = aws_key_pair.deployer.key_name

user_data = base64encode(templatefile(&quot;${path.module}/user_data.sh&quot;, {

db_endpoint = aws_db_instance.main.endpoint

}))

tags = {

Name = &quot;web-server&quot;

}

}

resource &quot;aws_key_pair&quot; &quot;deployer&quot; {

key_name = &quot;deployer-key&quot;

public_key = var.public_key

}</code></pre>

<p>Note que usamos <code>data.aws_ami</code> para buscar a AMI Ubuntu mais recente automaticamente — isto é melhor que hardcoding um AMI ID porque evita que seu código fique desatualizado. O <code>user_data</code> é um script que roda na primeira inicialização da instância; aqui usamos <code>templatefile</code> para injetar o endpoint do RDS dinamicamente.</p>

<h2>RDS: Banco de Dados Gerenciado</h2>

<p>RDS (Relational Database Service) é o serviço gerenciado de banco de dados da AWS. Você especifica o engine (MySQL, PostgreSQL, MariaDB, etc.), o tamanho da instância, armazenamento, e o Terraform cuida do resto — backup automático, patch management, replicação multi-AZ são todos configuráveis. O grande diferencial do RDS é que você não administra o servidor — AWS cuida de disponibilidade, performance e segurança no nível do sistema operacional.</p>

<p>Ao criar um RDS, você deve colocá-lo em uma subnet privada e nunca expor seu endpoint públicamente. O acesso deve vir apenas de instâncias EC2 que precisam se conectar a ele, controlado via security groups. Também é crítico configurar backup retention e habilitar multi-AZ para produção — isto garante que você tenha cópias e que o banco continue rodando se uma availability zone inteira cair.</p>

<pre><code class="language-hcl"># rds.tf

resource &quot;aws_db_subnet_group&quot; &quot;main&quot; {

name = &quot;main-db-subnet-group&quot;

subnet_ids = [aws_subnet.private.id, aws_subnet.private_az2.id]

tags = {

Name = &quot;main-db-subnet-group&quot;

}

}

resource &quot;aws_db_instance&quot; &quot;main&quot; {

identifier = &quot;myapp-db&quot;

allocated_storage = 20

db_name = var.db_name

engine = &quot;mysql&quot;

engine_version = &quot;8.0.35&quot;

instance_class = &quot;db.t3.micro&quot;

username = var.db_username

password = random_password.db_password.result

parameter_group_name = aws_db_parameter_group.main.name

skip_final_snapshot = false

final_snapshot_identifier = &quot;myapp-db-final-snapshot-${formatdate(&quot;YYYY-MM-DD-hhmm&quot;, timestamp())}&quot;

db_subnet_group_name = aws_db_subnet_group.main.name

vpc_security_group_ids = [aws_security_group.rds.id]

publicly_accessible = false

multi_az = true

backup_retention_days = 30

backup_window = &quot;03:00-04:00&quot;

maintenance_window = &quot;mon:04:00-mon:05:00&quot;

storage_encrypted = true

kms_key_id = aws_kms_key.rds.arn

enable_cloudwatch_logs_exports = [&quot;error&quot;, &quot;general&quot;, &quot;slowquery&quot;]

tags = {

Name = &quot;myapp-database&quot;

}

}

resource &quot;aws_db_parameter_group&quot; &quot;main&quot; {

family = &quot;mysql8.0&quot;

name = &quot;myapp-params&quot;

parameter {

name = &quot;character_set_server&quot;

value = &quot;utf8mb4&quot;

}

parameter {

name = &quot;collation_server&quot;

value = &quot;utf8mb4_unicode_ci&quot;

}

tags = {

Name = &quot;myapp-parameter-group&quot;

}

}

resource &quot;random_password&quot; &quot;db_password&quot; {

length = 16

special = true

}

resource &quot;aws_secretsmanager_secret&quot; &quot;db_password&quot; {

name = &quot;myapp/db/password&quot;

recovery_window_in_days = 7

}

resource &quot;aws_secretsmanager_secret_version&quot; &quot;db_password&quot; {

secret_id = aws_secretsmanager_secret.db_password.id

secret_string = random_password.db_password.result

}

resource &quot;aws_kms_key&quot; &quot;rds&quot; {

description = &quot;KMS key for RDS encryption&quot;

deletion_window_in_days = 10

enable_key_rotation = true

}

resource &quot;aws_kms_alias&quot; &quot;rds&quot; {

name = &quot;alias/myapp-rds&quot;

target_key_id = aws_kms_key.rds.key_id

}

resource &quot;aws_subnet&quot; &quot;private_az2&quot; {

vpc_id = aws_vpc.main.id

cidr_block = &quot;10.0.3.0/24&quot;

availability_zone = data.aws_availability_zones.available.names[1]

tags = {

Name = &quot;private-subnet-az2&quot;

}

}</code></pre>

<p>Vários pontos importantes aqui: usamos <code>random_password</code> para gerar uma senha segura e a armazenamos no AWS Secrets Manager, que é o lugar certo para guardar credenciais. O <code>skip_final_snapshot = false</code> força a criação de um snapshot antes de destruir o banco — isto salva você de acidentes catastróficos. Multi-AZ = true significa que o RDS é replicado sincronamente para outra availability zone. Encryption está habilitada com KMS, e exportamos logs do MySQL para CloudWatch para monitoramento e troubleshooting.</p>

<h2>IAM: Controle de Acesso Granular</h2>

<p>IAM (Identity and Access Management) é o sistema de controle de acesso da AWS. Você cria roles (conjuntos de permissões) e as associa a usuários, grupos, serviços ou, como fazemos aqui, a instâncias EC2. Uma role é composta de uma trust relationship (quem pode usar esta role) e policies (o que ela pode fazer).</p>

<p>O princípio fundamental do IAM é o <em>principle of least privilege</em> — uma aplicação ou usuário deve ter apenas as permissões que precisa para fazer seu trabalho. Se seu web server apenas precisa ler de S3 e escrever logs no CloudWatch, não dê permissão para deletar RDS ou modificar VPCs. Você vai criar policies detalhadas que especificam exatamente quais ações são permitidas em quais recursos.</p>

<pre><code class="language-hcl"># iam.tf

resource &quot;aws_iam_role&quot; &quot;ec2_role&quot; {

name = &quot;ec2-app-role&quot;

assume_role_policy = jsonencode({

Version = &quot;2012-10-17&quot;

Statement = [

{

Action = &quot;sts:AssumeRole&quot;

Effect = &quot;Allow&quot;

Principal = {

Service = &quot;ec2.amazonaws.com&quot;

}

}

]

})

}

resource &quot;aws_iam_policy&quot; &quot;cloudwatch_logs&quot; {

name = &quot;ec2-cloudwatch-logs-policy&quot;

description = &quot;Policy for EC2 to write logs to CloudWatch&quot;

policy = jsonencode({

Version = &quot;2012-10-17&quot;

Statement = [

{

Effect = &quot;Allow&quot;

Action = [

&quot;logs:CreateLogGroup&quot;,

&quot;logs:CreateLogStream&quot;,

&quot;logs:PutLogEvents&quot;,

&quot;logs:DescribeLogStreams&quot;

]

Resource = &quot;arn:aws:logs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:log-group:/myapp/*&quot;

}

]

})

}

resource &quot;aws_iam_policy&quot; &quot;s3_read&quot; {

name = &quot;ec2-s3-read-policy&quot;

description = &quot;Policy for EC2 to read from specific S3 bucket&quot;

policy = jsonencode({

Version = &quot;2012-10-17&quot;

Statement = [

{

Effect = &quot;Allow&quot;

Action = [

&quot;s3:GetObject&quot;,

&quot;s3:ListBucket&quot;

]

Resource = [

&quot;arn:aws:s3:::myapp-bucket&quot;,

&quot;arn:aws:s3:::myapp-bucket/*&quot;

]

}

]

})

}

resource &quot;aws_iam_policy&quot; &quot;secretsmanager_read&quot; {

name = &quot;ec2-secretsmanager-policy&quot;

description = &quot;Policy for EC2 to read database password from Secrets Manager&quot;

policy = jsonencode({

Version = &quot;2012-10-17&quot;

Statement = [

{

Effect = &quot;Allow&quot;

Action = [

&quot;secretsmanager:GetSecretValue&quot;

]

Resource = aws_secretsmanager_secret.db_password.arn

}

]

})

}

resource &quot;aws_iam_role_policy_attachment&quot; &quot;cloudwatch_logs&quot; {

role = aws_iam_role.ec2_role.name

policy_arn = aws_iam_policy.cloudwatch_logs.arn

}

resource &quot;aws_iam_role_policy_attachment&quot; &quot;s3_read&quot; {

role = aws_iam_role.ec2_role.name

policy_arn = aws_iam_policy.s3_read.arn

}

resource &quot;aws_iam_role_policy_attachment&quot; &quot;secretsmanager_read&quot; {

role = aws_iam_role.ec2_role.name

policy_arn = aws_iam_policy.secretsmanager_read.arn

}

resource &quot;aws_iam_instance_profile&quot; &quot;ec2_profile&quot; {

name = &quot;ec2-app-profile&quot;

role = aws_iam_role.ec2_role.name

}

data &quot;aws_caller_identity&quot; &quot;current&quot; {}</code></pre>

<p>Observe que cada policy é específica — a política CloudWatch Logs só permite escrever em log groups que começam com <code>/myapp/</code>, a S3 só permite ler do bucket <code>myapp-bucket</code>, e Secrets Manager só permite ler o segredo específico do banco de dados. Quando você cria a instância EC2 com <code>iam_instance_profile = aws_iam_instance_profile.ec2_profile.name</code>, a instância automaticamente pode usar essas permissões — não precisa de credentials hardcoded no código ou em arquivos de configuração.</p>

<h2>Variáveis, Outputs e Estrutura do Projeto</h2>

<p>Para manter seu código reutilizável e seguro, você deve parametrizar valores que mudam entre ambientes. Use <code>variables.tf</code> para declarar e <code>terraform.tfvars</code> (ou arquivos de variáveis específicos por ambiente) para fornecer valores. Nunca commite <code>terraform.tfvars</code> com valores sensíveis — use <code>git-crypt</code> ou <code>terraform cloud</code> para gerenciar secrets.</p>

<pre><code class="language-hcl"># variables.tf

variable &quot;aws_region&quot; {

description = &quot;AWS region&quot;

type = string

default = &quot;us-east-1&quot;

}

variable &quot;vpc_cidr&quot; {

description = &quot;CIDR block for VPC&quot;

type = string

default = &quot;10.0.0.0/16&quot;

}

variable &quot;db_name&quot; {

description = &quot;Database name&quot;

type = string

sensitive = true

}

variable &quot;db_username&quot; {

description = &quot;Database username&quot;

type = string

sensitive = true

}

variable &quot;public_key&quot; {

description = &quot;Public SSH key for EC2 access&quot;

type = string

sensitive = true

}

variable &quot;allowed_ssh_cidr&quot; {

description = &quot;CIDR block allowed to SSH to bastion&quot;

type = string

validation {

condition = can(cidrhost(var.allowed_ssh_cidr, 0))

error_message = &quot;allowed_ssh_cidr must be a valid CIDR block.&quot;

}

}</code></pre>

<pre><code class="language-hcl"># outputs.tf

output &quot;vpc_id&quot; {

description = &quot;ID of the VPC&quot;

value = aws_vpc.main.id

}

output &quot;bastion_public_ip&quot; {

description = &quot;Public IP of the bastion host&quot;

value = aws_instance.bastion.public_ip

}

output &quot;web_server_public_ip&quot; {

description = &quot;Public IP of the web server&quot;

value = aws_instance.web.public_ip

}

output &quot;rds_endpoint&quot; {

description = &quot;RDS database endpoint&quot;

value = aws_db_instance.main.endpoint

sensitive = true

}

output &quot;rds_port&quot; {

description = &quot;RDS database port&quot;

value = aws_db_instance.main.port

}

output &quot;database_password_secret_arn&quot; {

description = &quot;ARN of the Secrets Manager secret containing the database password&quot;

value = aws_secretsmanager_secret.db_password.arn

}</code></pre>

<p>Uma estrutura de projeto bem organizada fica assim:</p>

<pre><code>terraform/

├── main.tf # Configurações gerais

├── provider.tf # Provider AWS

├── variables.tf # Declarações de variáveis

├── outputs.tf # Declarações de outputs

├── terraform.tfvars # Valores das variáveis (NÃO commitar secrets aqui)

├── vpc.tf # Recursos de VPC

├── security_group.tf # Security groups

├── ec2.tf # Instâncias EC2

├── rds.tf # Banco de dados RDS

├── iam.tf # Roles e policies IAM

├── kms.tf # Chaves KMS para criptografia

├── user_data.sh # Script de inicialização da EC2

└── .terraform.lock.hcl # Lock file do Terraform (commitar)</code></pre>

<h2>Fluxo de Execução: Plan, Apply e Destroy</h2>

<p>Antes de aplicar qualquer mudança, você sempre executa <code>terraform plan</code> para ver o que será criado, modificado ou destruído. Este é seu safety net — revise cuidadosamente antes de aplicar.</p>

<pre><code class="language-bash"># Inicializar o diretório Terraform

terraform init

Ver o plano de execução

terraform plan -out=tfplan

Aplicar as mudanças

terraform apply tfplan

Verificar o estado

terraform state list

terraform state show aws_db_instance.main

Destruir toda a infraestrutura (use com cuidado!)

terraform destroy</code></pre>

<p>Um detalhe importante: <code>terraform state</code> é armazenado localmente em <code>terraform.tfstate</code> por padrão. Em produção, use <em>remote state</em> — guarde no S3 com versionamento e criptografia, ou use Terraform Cloud/Enterprise. Isto previne que alguém delete o arquivo local e perca o tracking da infraestrutura.</p>

<pre><code class="language-hcl"># backend.tf

terraform {

backend &quot;s3&quot; {

bucket = &quot;myapp-terraform-state&quot;

key = &quot;prod/terraform.tfstate&quot;

region = &quot;us-east-1&quot;

encrypt = true

dynamodb_table = &quot;terraform-locks&quot;

}

}</code></pre>

<h2>Conclusão</h2>

<p>Nesta aula, você aprendeu que <strong>Terraform permite descrever infraestrutura AWS de forma declarativa e versionável</strong>, transformando o gerenciamento de nuvem em código — é reproduzível, testável e colaborativo. Segundo, você compreendeu como <strong>VPC, EC2, RDS e IAM trabalham juntos</strong>: a VPC é o container de rede, EC2 são computadores nela, RDS é o banco de dados protegido em subnet privada, e IAM controla exatamente o que cada componente pode acessar. Terceiro, você aprendeu que <strong>segurança não é um add-on mas o fundamento</strong> — desde security groups que restringem tráfego até policies IAM granulares que seguem least privilege, cada decisão de arquitetura tem implicações de segurança.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs" target="_blank" rel="noopener noreferrer">Terraform AWS Provider Documentation</a></li>

<li><a href="https://docs.aws.amazon.com/vpc/latest/userguide/" target="_blank" rel="noopener noreferrer">AWS VPC User Guide</a></li>

<li><a href="https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_BestPractices.html" target="_blank" rel="noopener noreferrer">AWS RDS Best Practices</a></li>

<li><a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html" target="_blank" rel="noopener noreferrer">AWS IAM Best Practices</a></li>

<li><a href="https://developer.hashicorp.com/terraform/language/state" target="_blank" rel="noopener noreferrer">Terraform State Management</a></li>

</ul>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em DevOps & CI/CD

Guia Completo de Grafana: Dashboards, Data Sources e Alertas Visuais
Guia Completo de Grafana: Dashboards, Data Sources e Alertas Visuais

Introdução ao Grafana e sua Importância no Monitoramento Moderno Grafana é um...

Como Usar Estratégias de Deploy em Kubernetes: Rolling, Blue-Green e Canary em Produção
Como Usar Estratégias de Deploy em Kubernetes: Rolling, Blue-Green e Canary em Produção

Introdução: Por que Estratégias de Deploy Importam em Kubernetes Quando você...

O que Todo Dev Deve Saber sobre OpenTelemetry: Instrumentação Unificada para Métricas, Logs e Traces
O que Todo Dev Deve Saber sobre OpenTelemetry: Instrumentação Unificada para Métricas, Logs e Traces

O que é OpenTelemetry e por que você precisa dele OpenTelemetry é um framewor...