<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Rina Villaruz on Medium]]></title>
        <description><![CDATA[Stories by Rina Villaruz on Medium]]></description>
        <link>https://medium.com/@rinavillaruz?source=rss-2f420b86b694------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*HT3KoTAD1aEzQ9fdXmB-uQ.jpeg</url>
            <title>Stories by Rina Villaruz on Medium</title>
            <link>https://medium.com/@rinavillaruz?source=rss-2f420b86b694------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Thu, 28 May 2026 17:08:25 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@rinavillaruz/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Deploy Simple AWS Infrastructure Using Terraform]]></title>
            <link>https://medium.com/@rinavillaruz/deploy-simple-aws-infrastructure-using-terraform-86704a111da0?source=rss-2f420b86b694------2</link>
            <guid isPermaLink="false">https://medium.com/p/86704a111da0</guid>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[terraform]]></category>
            <dc:creator><![CDATA[Rina Villaruz]]></dc:creator>
            <pubDate>Fri, 22 Aug 2025 06:26:34 GMT</pubDate>
            <atom:updated>2025-08-22T06:26:34.677Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0D_b2cv6BmeJWhjKwc0cQw.jpeg" /></figure><p>Building cloud infrastructure used to mean spending hours clicking through the AWS console and trying to remember which settings you used last time. I’ve been there — frantically taking screenshots of configurations and keeping messy notes just to recreate environments.</p><p>That’s where Terraform comes in. Instead of all that manual work, you write a few configuration files that describe exactly what you want your infrastructure to look like, and Terraform handles the rest.</p><p>Here’s what I am going to build:</p><ul><li>A VPC</li><li>Public subnets for things that need internet access</li><li>Private subnets for your databases and sensitive stuff</li><li>All the networking pieces to make everything talk to each other</li><li>Security groups to keep the bad guys out</li><li>3 EC2 instances</li></ul><p>What you’ll need before we start:</p><ul><li>An AWS account (the free tier works fine for this / elastic ip is around i think $0.005 per hr / if you have free aws credits, this won’t hurt)</li><li>Terraform installed on your computer</li><li>AWS CLI set up with your credentials</li><li>About an hour of your time</li></ul><p>Let’s setup the environment first. Go to IAM &gt; Create a User &gt; Security Credentials Tab &gt; Create Access Key. Create an AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY.</p><pre>AWS_ACCESS_KEY_ID=12345678<br>AWS_SECRET_ACCESS_KEY=12345678</pre><p>Create a docker-compose file that creates a containerized Terraform environment with AWS CLI. The latest image of terraform will be used, make sure to mount the current directory to /workspace, and stdin_open and tty are set to true so that it will enable terminal access.</p><pre>services:<br>  terraform:<br>    image: hashicorp/terraform:latest<br>    working_dir: /workspace<br>    container_name: terraform-aws<br>    entrypoint: [&quot;sh&quot;, &quot;-c&quot;, &quot;apk add --no-cache aws-cli &amp;&amp; sleep infinity&quot;]<br>    volumes:<br>      - .:/workspace<br>    stdin_open: true<br>    tty: true<br>    environment:<br>      - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}<br>      - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}<br>      - AWS_REGION=us-east-1</pre><p>Run docker-compose up -d to start, then docker exec -it terraform-aws sh to access the container.</p><pre>docker-compose up -d<br>docker exec -it terraform-aws sh</pre><p>And this is the Terraform Project Structure:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4H3R7WQ5I63M_Hud_hnPGA.png" /></figure><h3>Key Pairs</h3><p>Key Pairs are cryptographic SSH keys used to securely authenticate and access EC2 instances without passwords. Key Pairs are like the master key and ID badges that let you securely access your employees or EC2 instances in your office building. Even if someone breaks into your building, they can’t access your employee’s work stations without the specific private badge reader. It’s important to note if you lose the private key file, you lose ssh access to your EC2 instance permanently or you can setup alternative access methods.</p><p>Generates a 4096-bit RSA private/public key pair in memory.</p><pre>resource &quot;tls_private_key&quot; &quot;private&quot; {<br>    algorithm   =   &quot;RSA&quot;<br>    rsa_bits    =   4096<br>}</pre><p>Registers the public key with AWS as an EC2 key pair named “terraform-key-pair”.</p><pre>resource &quot;aws_key_pair&quot; &quot;generated_key&quot; {<br>    key_name    =   &quot;terraform-key-pair&quot;<br>    public_key  =   tls_private_key.private.public_key_openssh<br>}</pre><p>Saves the private key as a .pem file to your local Terraform directory.</p><pre>resource &quot;local_file&quot; &quot;private_key&quot; {<br>    content     =   tls_private_key.private.private_key_pem<br>    filename    =   &quot;${path.root}/terraform-key-pair.pem&quot;<br>}</pre><p>Expose the return values of the above code to be used by other modules.</p><pre>output &quot;key_pair_name&quot; {<br>    value = aws_key_pair.generated_key.key_name<br>}<br><br>output &quot;tls_private_key_pem&quot; {<br>    value = tls_private_key.private.private_key_pem<br>}</pre><p>Create the keypair module.</p><pre>module &quot;keypair&quot; {<br>    source          =   &quot;../../modules/keypair&quot;<br>}</pre><p>You can see the full code here <a href="https://github.com/rinavillaruz/easy-aws-infrastructure-terraform">https://github.com/rinavillaruz/easy-aws-infrastructure-terraform</a>.</p><h3>Networking</h3><h4>VPC</h4><p>A VPC is a logically isolated section of a cloud provider’s network where you can launch and manage your cloud resources in a virtual network that you define and control. They are fundamental to cloud architecture because they can give you the network foundation needed to build a secure and scalable applications while maintaining control over your network environment. Like an office building floor, you can rent an entire floor of a sky scraper, you can decide how to divide it into rooms or subnets, who can access each rooms or security groups and weather some rooms have windows to the outside world or internet access. While others are interior offices or private subnets. It’s essentially cloud providers saying “here’s your own private piece of the internet where you can build whatever you need.</p><p>Create a VPC with 10.0.0.0/16 CIDR block.</p><pre>resource &quot;aws_vpc&quot; &quot;main&quot; {<br>  cidr_block = &quot;10.0.0.0/16&quot;<br><br>  tags = {<br>    Name = &quot;VPC&quot;<br>  }<br>}</pre><p>Expose the return values of the above code to be used by other modules.</p><pre>output &quot;vpc_id&quot; {<br>  value = aws_vpc.main.id<br>}<br><br>output &quot;vpc_cidr_block&quot; {<br>  value = aws_vpc.main.cidr_block<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8oqqV14c6mLhjAouEcB4UQ.png" /></figure><h4>Public Subnets</h4><p>A Public Subnet has a route to an Internet Gateway. Think of it as a building’s main entrance that connects your floor directly to the street. Any server you put in a public subnet can receive traffic directly from the internet. Just like how people on the street can see and access those street-facing conference rooms. You have to note that just because a room has windows, doesn’t mean anyone can just walk in. You still have security or security groups, firewalls controlling exactly who can enter and what they can do.</p><p>Defines an input variable that accepts a list of subnet CIDR blocks, defaulting to one subnet (10.0.1.0/24).</p><pre>variable &quot;public_subnet_cidrs&quot; {<br>    type = list(string)<br>    default = [ &quot;10.0.1.0/24&quot; ]<br>}</pre><p>Defines an input variable that accepts a list of availability zones, defaulting to us-east-1a and us-east-1b.</p><pre>variable &quot;azs&quot; {<br>    type = list(string)<br>    default = [ &quot;us-east-1a&quot;, &quot;us-east-1b&quot; ]<br>}</pre><p>Creates multiple public subnets in the VPC, one for each CIDR block in the variable, distributing them across the specified availability zones.</p><pre>resource &quot;aws_subnet&quot; &quot;public_subnets&quot; {<br>  count             = length(var.public_subnet_cidrs)<br>  vpc_id            = aws_vpc.main.id<br>  cidr_block        = element(var.public_subnet_cidrs, count.index)<br>  availability_zone = element(var.azs, count.index)<br><br>  tags = {<br>    Name = &quot;Public Subnet ${count.index+1}&quot;<br>  }<br>}</pre><p>Expose the return values of the above code to be used by other modules.</p><pre>output &quot;public_subnets&quot; {<br>  value = aws_subnet.public_subnets<br>}<br><br>output public_subnet_cidrs {<br>  value = var.public_subnet_cidrs<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vvOIa4gtqWP2LppFsY55aQ.png" /></figure><h4>Private Subnets</h4><p>A Private Subnet is like the interior offices and back rooms. They have no windows to the street and can’t be accessed directly from the outside world. It has no direct route to the internet gateway. There’s no street entrance to these rooms. These servers can’t receive traffic directly from the internet just like how people on the street cant walk directly in to your back offices. To be able for private subnet to access the outside world, it must go through NAT Gateway which we will not cover. Even if someone breaks through your perimeter security, your most critical systems: databases, internal applications, are in these window-less backrooms. Where they are much harder to reach and attack directly.</p><p>Defines an input variable for private subnet CIDR blocks, defaulting to two subnets (10.0.2.0/24 and 10.0.3.0/24).</p><pre>variable &quot;private_subnet_cidrs&quot; {<br>  type = list(string)<br>  default = [ &quot;10.0.2.0/24&quot;, &quot;10.0.3.0/24&quot; ]<br>}</pre><p>Creates private subnets using the CIDR blocks and availability zones from variables, limited by whichever list is shorter.</p><pre>resource &quot;aws_subnet&quot; &quot;private_subnets&quot; {<br>  count             = min(length(var.private_subnet_cidrs), length(var.azs))<br>  vpc_id            = aws_vpc.main.id<br>  cidr_block        = var.private_subnet_cidrs[count.index]<br>  availability_zone = var.azs[count.index]<br><br>  tags = {<br>    Name = &quot;Private Subnet ${count.index + 1}&quot;<br>  }<br>}</pre><p>Expose the return values of the above code to be used by other modules.</p><pre>output &quot;private_subnets&quot; {<br>  value = aws_subnet.private_subnets<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hUL2cTJMYweD49z_2Jcguw.png" /></figure><p>You can see the full code here <a href="https://github.com/rinavillaruz/easy-aws-infrastructure-terraform">https://github.com/rinavillaruz/easy-aws-infrastructure-terraform</a>.</p><h4>Internet Gateway</h4><p>An Internet Gateway is like the main building entrance and lobby. Its a single point where your office floors connect to the outside world or the internet. Simply put, its an aws-managed component that connects your VPC to the internet. Without Internet Gateway, your VPC has no internet connectivity at all — completely isolated network.</p><p>Creates an internet gateway and attaches it to the VPC to enable internet access.</p><pre>resource &quot;aws_internet_gateway&quot; &quot;igw&quot; {<br>  vpc_id  = aws_vpc.main.id<br><br>  tags = {<br>    Name = &quot;Internet Gateway&quot;<br>  }<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UFcOUR1OxzdAgfX6NexDvA.png" /></figure><h4>Route Tables</h4><p>Route Tables are network routing rules that determine where to send traffic based on destination IP addresses. When your web servers wants to download updates from the internet, it checks it’s route table. To reach 0.0.0.0/0, go to the Internet Gateway and sends the traffic there.</p><p>Creates a public route table in the VPC.</p><pre>resource &quot;aws_route_table&quot; &quot;public&quot; {<br>  vpc_id = aws_vpc.main.id<br><br>  tags = {<br>    Name = &quot;Public Route Table&quot;<br>  }<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*i4wUOCItCawq_4bQ3FoXRA.png" /></figure><p>Adds a route in the public route table that sends all traffic (0.0.0.0/0) to the internet gateway.</p><pre>resource &quot;aws_route&quot; &quot;public_internet_access&quot; {<br>  route_table_id          = aws_route_table.public.id<br>  destination_cidr_block  = &quot;0.0.0.0/0&quot;<br>  gateway_id              = aws_internet_gateway.igw.id<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yQoHwlXJFbrAIuHjEbLrPg.png" /></figure><p>Associates the first public subnet with the public route table.</p><pre>resource &quot;aws_route_table_association&quot; &quot;public_first_subnet&quot; {<br>  subnet_id       = aws_subnet.public_subnets[0].id<br>  route_table_id  = aws_route_table.public.id<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*j7GnbRiPXzGhgFu37SJuLQ.png" /></figure><p>Creates a private route table in the VPC.</p><pre>resource &quot;aws_route_table&quot; &quot;private&quot; {<br>  vpc_id = aws_vpc.main.id<br><br>  tags = {<br>    Name = &quot;Private Route Table&quot;<br>  }<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xCLeCOq_PkvsdoIwKFaFAA.png" /></figure><p>Associates all private subnets with the private route table.</p><pre>resource &quot;aws_route_table_association&quot; &quot;private&quot; {<br>  count           = length(var.private_subnet_cidrs)<br>  subnet_id       = element(aws_subnet.private_subnets[*].id, count.index)<br>  route_table_id  = aws_route_table.private.id<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VKZop77BkCWDiIVQbgpb1Q.png" /></figure><p>Create the networking module.</p><pre>module &quot;networking&quot; {<br>    source          =   &quot;../../modules/networking&quot;<br>}</pre><p>You can see the full code here <a href="https://github.com/rinavillaruz/easy-aws-infrastructure-terraform">https://github.com/rinavillaruz/easy-aws-infrastructure-terraform</a>.</p><h3>Security Groups</h3><p>Security Groups are virtual firewalls that control inbound and outbound traffic at the instance level like EC2 and RDS. Unlike the building’s main security that controls who can enter each room or area, security groups are like individual bodyguards that stick with specific employees wherever they go. For example, if someone approaches your employee, start a conversation which is called the <strong>inbound</strong> request and you bodyguard approves it, they automatically allow that person to respond back which is called the <strong>outbound</strong> response.</p><p>The Security Groups module will be accepting returned values from the Networking module which are the vpc_id and vpc_cidr_block. Create a variable for them.</p><pre>variable &quot;vpc_id&quot; {<br>  type = string<br>}<br><br>variable &quot;vpc_cidr_block&quot; {<br>  type = string<br>}</pre><p>Creates a public security group in the VPC.</p><pre>resource &quot;aws_security_group&quot; &quot;public&quot; {<br>  name    = &quot;public-sg&quot;<br>  vpc_id  = var.vpc_id<br><br>  tags = {<br>    Name = &quot;Public SG&quot;<br>  }<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CWMhzWjGIg-HxquUl-Wmug.png" /></figure><p>Creates a private security group in the VPC.</p><pre>resource &quot;aws_security_group&quot; &quot;private&quot; {<br>  name    = &quot;private-sg&quot;<br>  vpc_id  = var.vpc_id<br><br>  tags = {<br>    Name = &quot;Private SG&quot;<br>  }<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*r9nkgwDt9dXEicrdmSH8YQ.png" /></figure><p>Allows SSH outbound traffic from the public security group to the private security group.</p><pre>resource &quot;aws_vpc_security_group_egress_rule&quot; &quot;public_egress&quot; {<br>  security_group_id             = aws_security_group.public.id<br>  from_port                     = 22<br>  to_port                       = 22<br>  ip_protocol                   = &quot;tcp&quot;<br>  referenced_security_group_id  = aws_security_group.private.id<br><br>  tags = {<br>    Name = &quot;SSH Outgoing - Public SG -&gt; Private SG&quot;<br>  }<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PIs4D-D69QKLPdbC5xOOjw.png" /></figure><p>Allows SSH inbound traffic to the private security group from the public security group.</p><pre>resource &quot;aws_vpc_security_group_ingress_rule&quot; &quot;private_ingress&quot; {<br>  security_group_id             = aws_security_group.private.id<br>  from_port                     = 22<br>  to_port                       = 22<br>  ip_protocol                   = &quot;tcp&quot;<br>  referenced_security_group_id  = aws_security_group.public.id<br><br>  tags = {<br>    Name = &quot;SSH Incoming - Private SG &lt;- Public SG&quot;<br>  }<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hr2mT9t2OTmvw-VR0q_gYA.png" /></figure><p>Allows SSH inbound traffic to the public security group from anywhere on the internet.</p><pre>resource &quot;aws_vpc_security_group_ingress_rule&quot; &quot;public_ssh_anywhere&quot; {<br>  security_group_id = aws_security_group.public.id<br>  from_port         = 22<br>  to_port           = 22<br>  ip_protocol       = &quot;tcp&quot;<br>  cidr_ipv4         = &quot;0.0.0.0/0&quot;<br><br>  tags = {<br>    Name = &quot;Public SG SSH Anywhere&quot;<br>  }<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2DjXXoANRJcTKnaQQjpwHA.png" /></figure><p>Create the security_groups module, passing the VPC ID and CIDR block from the networking module, and waits for networking to complete first.</p><pre>module security_groups {<br>  source            =   &quot;../../modules/security_groups&quot;<br><br>  vpc_id            =   module.networking.vpc_id<br>  vpc_cidr_block    =   module.networking.vpc_cidr_block<br><br>  depends_on        =   [ module.networking ]<br>}</pre><p>You can see the full code here <a href="https://github.com/rinavillaruz/easy-aws-infrastructure-terraform">https://github.com/rinavillaruz/easy-aws-infrastructure-terraform</a>.</p><h3>EC2 Instances</h3><p>Just like you hire different types of employees with different skills and assign them to different rooms in your office, EC2 instances are virtual computers that you hire from AWS that performs specific tasks. EC2 instances are your actual workforce. The virtual computers doing their real work in your cloud infrastructure just like employees doing real work in your office building.</p><p>Defines the private IP address for the public instance, defaulting to 10.0.1.10.</p><pre>variable &quot;public_instance_private_ip&quot; {<br>  type = string<br>  default = &quot;10.0.1.10&quot;<br>}</pre><p>Defines the private IP addresses for private instances, defaulting to 10.0.2.10 and 10.0.3.10.</p><pre>variable &quot;private_instance_private_ips&quot; {<br>  type = list(string)<br>  default = [ &quot;10.0.2.10&quot;, &quot;10.0.3.10&quot; ]<br>}</pre><p>The compute module will be accepting returned values from the other modules. Create variables for them.</p><pre>variable &quot;vpc_id&quot; {<br>  type = string<br>}<br><br>variable &quot;private_subnets&quot; {<br>  type = any<br>}<br><br>variable &quot;public_subnets&quot; {<br>  type = any<br>}<br><br>variable &quot;public_security_group_id&quot; {<br>  type = string<br>}<br><br>variable &quot;private_security_group_id&quot; {<br>  type = string<br>}<br><br>variable &quot;tls_private_key_pem&quot; {<br>  type = string<br>  sensitive = true<br>}<br><br>variable &quot;key_pair_name&quot; {<br>  type = string<br>}<br><br>variable &quot;public_subnet_cidrs&quot; {<br>  type = list(string)<br>}</pre><p>Creates public EC2 instances in each public subnet with the specified AMI, instance type, and security group.</p><pre>resource &quot;aws_instance&quot; &quot;public&quot; {<br>  count                   = length(var.public_subnet_cidrs)<br>  ami                     = &quot;ami-084568db4383264d4&quot;<br>  instance_type           = &quot;t3.micro&quot;<br>  key_name                = var.key_pair_name<br>  vpc_security_group_ids  = [var.public_security_group_id]<br>  subnet_id               = var.public_subnets[count.index].id <br>  private_ip              = var.public_instance_private_ip<br><br>  tags = {<br>    Name = &quot;Public Instance&quot;<br>  }<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yUCa12eHW32mp4F3apbfJg.png" /></figure><p>Creates Elastic IP addresses for each public instance.</p><pre>resource &quot;aws_eip&quot; &quot;public_eip&quot; {<br>  count   = length(var.public_subnet_cidrs)<br>  domain  = &quot;vpc&quot;<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XIg0mGZ5pRHlqxwDM6L61A.png" /></figure><p>Associates each Elastic IP with its corresponding public instance.</p><pre>resource &quot;aws_eip_association&quot; &quot;public_eip_assoc&quot; {<br>  count         = length(var.public_subnet_cidrs)<br>  instance_id   = aws_instance.public[count.index].id<br>  allocation_id = aws_eip.public_eip[count.index].id<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6lv68X29C5kV7yPphQFpyg.png" /></figure><p>Creates private EC2 instances in private subnets with custom root volumes and assigned private IPs.</p><pre>resource &quot;aws_instance&quot; &quot;private_instances&quot; {<br>  for_each                = { for index, ip in var.private_instance_private_ips : index =&gt; ip }<br>  ami                     = &quot;ami-084568db4383264d4&quot;<br>  instance_type           = &quot;t3.micro&quot;<br>  key_name                = var.key_pair_name<br>  vpc_security_group_ids  = [var.private_security_group_id]<br>  subnet_id               = var.private_subnets[each.key].id<br>  private_ip              = var.private_instance_private_ips[each.key]<br><br>  root_block_device {<br>    volume_size           = 20<br>    volume_type           = &quot;gp3&quot;<br>    delete_on_termination = true<br>  }<br><br>  tags = {<br>    Name = &quot;Private Instance ${each.key + 1}&quot;<br>  }<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IUlghF_8rNMUVqjBIiM5TQ.png" /></figure><p>Creates the compute module, passing networking details, security group IDs, and key pair information from other modules, and waits for all dependencies to complete first.</p><pre>module &quot;compute&quot; {<br>  source                    =   &quot;../../modules/compute&quot;<br><br>  vpc_id                    =   module.networking.vpc_id<br>  public_subnet_cidrs       =   module.networking.public_subnet_cidrs<br>  private_subnets           =   module.networking.private_subnets<br>  public_subnets            =   module.networking.public_subnets<br>  public_security_group_id  =   module.security_groups.public_security_group_id<br>  private_security_group_id =   module.security_groups.private_security_group_id<br>  key_pair_name             =   module.keypair.key_pair_name<br>  tls_private_key_pem       =   module.keypair.tls_private_key_pem<br><br>  depends_on                =   [ module.keypair, module.networking, module.security_groups ]<br>}</pre><h3>Policies</h3><p>In aws, create a terraform-user and add these policies:</p><pre>{<br> &quot;Version&quot;: &quot;2012-10-17&quot;,<br> &quot;Statement&quot;: [<br>  {<br>   &quot;Effect&quot;: &quot;Allow&quot;,<br>   &quot;Action&quot;: [<br>    &quot;ec2:DescribeVpcAttribute&quot;,<br>    &quot;ec2:DescribeInstanceTypes&quot;,<br>    &quot;ec2:DescribeAddressesAttribute&quot;,<br>    &quot;ec2:CreateVpc&quot;,<br>    &quot;ec2:DeleteVpc&quot;,<br>    &quot;ec2:DescribeVpcs&quot;,<br>    &quot;ec2:ModifyVpcAttribute&quot;,<br>    &quot;ec2:CreateSubnet&quot;,<br>    &quot;ec2:DeleteSubnet&quot;,<br>    &quot;ec2:DescribeSubnets&quot;,<br>    &quot;ec2:CreateInternetGateway&quot;,<br>    &quot;ec2:DeleteInternetGateway&quot;,<br>    &quot;ec2:DescribeInternetGateways&quot;,<br>    &quot;ec2:AttachInternetGateway&quot;,<br>    &quot;ec2:DetachInternetGateway&quot;,<br>    &quot;ec2:CreateRouteTable&quot;,<br>    &quot;ec2:DeleteRouteTable&quot;,<br>    &quot;ec2:DescribeRouteTables&quot;,<br>    &quot;ec2:AssociateRouteTable&quot;,<br>    &quot;ec2:DisassociateRouteTable&quot;,<br>    &quot;ec2:CreateRoute&quot;,<br>    &quot;ec2:DeleteRoute&quot;,<br>    &quot;ec2:CreateSecurityGroup&quot;,<br>    &quot;ec2:DeleteSecurityGroup&quot;,<br>    &quot;ec2:DescribeSecurityGroups&quot;,<br>    &quot;ec2:DescribeSecurityGroupRules&quot;,<br>    &quot;ec2:AuthorizeSecurityGroupIngress&quot;,<br>    &quot;ec2:AuthorizeSecurityGroupEgress&quot;,<br>    &quot;ec2:RevokeSecurityGroupIngress&quot;,<br>    &quot;ec2:RevokeSecurityGroupEgress&quot;,<br>    &quot;ec2:CreateKeyPair&quot;,<br>    &quot;ec2:DeleteKeyPair&quot;,<br>    &quot;ec2:DescribeKeyPairs&quot;,<br>    &quot;ec2:ImportKeyPair&quot;,<br>    &quot;ec2:RunInstances&quot;,<br>    &quot;ec2:TerminateInstances&quot;,<br>    &quot;ec2:DescribeInstances&quot;,<br>    &quot;ec2:DescribeInstanceAttribute&quot;,<br>    &quot;ec2:AllocateAddress&quot;,<br>    &quot;ec2:ReleaseAddress&quot;,<br>    &quot;ec2:DescribeAddresses&quot;,<br>    &quot;ec2:AssociateAddress&quot;,<br>    &quot;ec2:DisassociateAddress&quot;,<br>    &quot;ec2:CreateTags&quot;,<br>    &quot;ec2:DescribeTags&quot;,<br>    &quot;ec2:DescribeAvailabilityZones&quot;,<br>    &quot;ec2:DescribeImages&quot;,<br>    &quot;ec2:DescribeVolumes&quot;,<br>    &quot;ec2:DescribeInstanceCreditSpecifications&quot;,<br>    &quot;ec2:DescribeNetworkInterfaces&quot;<br>   ],<br>   &quot;Resource&quot;: &quot;*&quot;<br>  }<br> ]<br>}</pre><p>Run the following to see the changes.</p><pre>terraform init<br>terraform plan -out=aws.tfplan<br>terraform apply aws.tfplan</pre><p><a href="https://videopress.com/v/sTxwraGb">terraform-view-mov</a></p><h3>Destroy</h3><p>If you want to destroy everything, run</p><pre>terraform destroy --auto-approve</pre><p>You can see this on Github <a href="https://github.com/rinavillaruz/easy-aws-infrastructure-terraform">https://github.com/rinavillaruz/easy-aws-infrastructure-terraform</a>.</p><p>You can see it on my blog <a href="https://rinavillaruz.com/2025/08/08/deploy-simple-aws-infrastructure-using-terraform/">https://rinavillaruz.com/2025/08/08/deploy-simple-aws-infrastructure-using-terraform/</a></p><p><a href="https://rinavillaruz.com/2025/08/08/deploy-simple-aws-infrastructure-using-terraform/">Deploy Simple AWS Infrastructure Using Terraform</a></p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FawtX_G44NOU%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DawtX_G44NOU&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FawtX_G44NOU%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/960c243c9a60caa6d1422bb4c51c4bf5/href">https://medium.com/media/960c243c9a60caa6d1422bb4c51c4bf5/href</a></iframe><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=86704a111da0" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Deploy Kubernetes using Terraform and Amazon Web Services (AWS)]]></title>
            <link>https://medium.com/@rinavillaruz/deploy-kubernetes-using-terraform-and-amazon-web-services-aws-f9987b4a04df?source=rss-2f420b86b694------2</link>
            <guid isPermaLink="false">https://medium.com/p/f9987b4a04df</guid>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[terraform]]></category>
            <dc:creator><![CDATA[Rina Villaruz]]></dc:creator>
            <pubDate>Mon, 04 Aug 2025 14:57:06 GMT</pubDate>
            <atom:updated>2025-08-04T14:57:06.056Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*u-DIBpP5e73AaeCYv6Lizg.jpeg" /></figure><p>This is one of the hard ways to install and run Kubernetes. I recommend this for learning purposes and not for production use. There is an Amazon EKS (Elastic Kubernetes Service) which you can use rather than setting up your own just like this tutorial.</p><p>First, the <strong>VPC</strong>, <strong>Subnets</strong>, <strong>Security Groups</strong>, <strong>Key Pairs</strong>, <strong>SSM</strong>, <strong>IAM roles</strong>, <strong>Network Load Balancer</strong>, <strong>EC2 Instances</strong> (1 Bastion host, 3 Control Planes, 3 Worker Nodes) need to be setup first before Kubernetes can be installed through bash scripting.</p><p>Terraform will be used to spin AWS resources. It is an Infrastructure-as-code that lets you create AWS resources without having to provision them manually by mouse clicks.</p><p>Table of Contents:</p><p>Key Pair</p><p>IAM</p><p>SSM</p><p>Networking</p><p>VPC</p><p>Public Subnet</p><p>Private Subnet</p><p>Internet Gateway</p><p>Public Route Table</p><p>Private Route Table</p><p>Elastic IP for NAT Gateway</p><p>NAT Gateway</p><p>Security Groups</p><p>EC2 Instances</p><p>Kubernetes Control Planes Installation using Bash Script</p><p>Kubernetes Master Control Plane Wait Script</p><p>Kubernetes Worker Nodes Installation using Bash Script</p><p>Kubernetes Wait for Worker Node Script</p><p>Kubernetes Worker Node Labelling Script</p><p>Common Functions Script</p><h3>Key Pair</h3><p>Key Pairs are secure authentication method for accessing EC2 instances via SSH. It consists of Public and Private Keys.</p><ul><li><strong>Public Key</strong>: Gets installed on EC2 instances during launch</li><li><strong>Private Key</strong>: Stays on your local machine (like a secret password)</li></ul><p>Create the Key Pairs first, name it as terraform-key-pair.pem and save it locally.</p><pre># modules/keypair/main.tf<br><br># Generate an RSA key pair<br>resource &quot;tls_private_key&quot; &quot;private&quot; {<br>  algorithm = &quot;RSA&quot;<br>  rsa_bits  = 4096<br>}<br><br># Create an AWS key pair using the generated public key<br>resource &quot;aws_key_pair&quot; &quot;generated_key&quot; {<br>  key_name   = &quot;terraform-key-pair&quot;<br>  public_key = tls_private_key.private.public_key_openssh<br>}<br><br># Save the private key locally<br>resource &quot;local_file&quot; &quot;private_key&quot; {<br>  content  = tls_private_key.private.private_key_pem<br>  filename = &quot;${path.root}/terraform-key-pair.pem&quot;<br>}</pre><p>Expose the return values of the above code using outputs to be used on other modules.</p><pre># modules/keypair/outputs.tf<br><br>output &quot;key_pair_name&quot; {  <br>  description = &quot;Name of the AWS key pair for SSH access to EC2 instances&quot;<br>  value       = aws_key_pair.generated_key.key_name<br>}<br><br>output &quot;tls_private_key_pem&quot; {  <br>  description = &quot;Private key in PEM format for SSH access - keep secure and do not expose&quot;<br>  value       = tls_private_key.private.private_key_pem<br>  sensitive   = true<br>}</pre><p>Create a custom module named <strong>keypair</strong>.</p><pre># environment/development/main.tf<br><br>module keypair {<br>  source = &quot;../../modules/keypair&quot;<br>}</pre><h3>IAM</h3><p>IAM (Identity Access Management) is AWS’s security system where in it controls who can do what in an AWS account. IAM can do two things: Authenticate (who are you?) and Authorize (what can you do?).</p><p><strong>Authentication</strong><br>– <strong>Users</strong>: Individual people (e.g., developers)<br>– <strong>Roles</strong>: Temporary identities for services/applications<br>– <strong>Groups</strong>: Collections of users with similar permissions</p><p><strong>Authorization</strong><br><strong>– Policies</strong>: Rules that define permissions<br><strong>– Permissions</strong>: Specific actions allowed/denied</p><p>Example IAM Users:</p><pre>John (Developer) -&gt; Can create EC2 instances but not delete them<br>Sarah (Admin) -&gt; Can do everything<br>CI/CD System -&gt; Can deploy applications but not manage billing</pre><p>Example IAM Roles:</p><pre>EC2 Instance Role -&gt; Can read from S3 buckets<br>Lambda Function Role -&gt; Can write to DynamoDB<br>Kubernetes Node Role -&gt; Can join cluster and pull images</pre><p>Example Policies:</p><pre>{<br>  &quot;Effect&quot;: &quot;Allow&quot;,<br>  &quot;Action&quot;: &quot;ec2:DescribeInstances&quot;,<br>  &quot;Resource&quot;: &quot;*&quot;<br>}</pre><ol><li>data &quot;aws_caller_identity&quot; &quot;current&quot; {}</li></ol><p>Gets information about the <strong>current AWS account and user</strong> that Terraform is using. It’s like asking “Who am I?” to AWS. It provides:</p><ul><li><strong>Account ID</strong>: The AWS account number (like 123456789012)</li><li><strong>User ID</strong>: The unique identifier of the current user/role</li><li><strong>ARN</strong>: The full Amazon Resource Name of the current user/role</li></ul><pre># modules/iam/main.tf<br><br># IAM<br>data &quot;aws_caller_identity&quot; &quot;current&quot; {}</pre><p>Example usage:</p><pre>&quot;arn:aws:ssm:us-east-1:${data.aws_caller_identity.current.account_id}:parameter/k8s/*&quot;</pre><p>2. resource &quot;random_id&quot; &quot;cluster&quot; { byte_length = 4 }</p><p>Generates a <strong>random identifier</strong> that will be consistent across Terraform runs. It’s like creating a unique “serial number” for your cluster. This ensures <strong>all resources are uniquely named and belong to the same cluster deployment</strong>. What it does:</p><ul><li><strong>Creates</strong>: A random 4-byte (32-bit) identifier</li><li><strong>Formats</strong>: Usually displayed as hexadecimal (like a1b2c3d4)</li><li><strong>Persistence</strong>: Same value every time you run terraform apply (unless you destroy and recreate)</li></ul><p>It is used in aws_iam_role.kubernetes_master, aws_iam_instance_profile.kubernetes_master, aws_iam_role.kubernetes_worker, aws_iam_instance_profile.kubernetes_worker.</p><p>This is used to avoid naming conflicts. When multiple people or environments deploy the same Terraform code, IAM resources need <strong>unique names</strong> because:</p><ul><li><strong>IAM names are globally unique within an AWS account</strong></li><li><strong>Multiple deployments would conflict</strong> without unique identifiers</li><li><strong>Easy identification</strong> of which resources belong to which cluster</li></ul><p>If<strong> Consistent Random Suffixes</strong> are implemented there will be:</p><ul><li><strong>No Conflicts</strong>: Multiple developers/environments can deploy simultaneously</li><li><strong>Easy Cleanup</strong>: All resources for one cluster have the same suffix</li><li><strong>Clear Ownership</strong>: Can identify which resources belong to which deployment</li><li><strong>Testing</strong>: Can deploy multiple test environments without conflicts</li></ul><pre>resource &quot;random_id&quot; &quot;cluster&quot; {<br>  byte_length = 4<br>}</pre><h3>Control Plane Master IAM Setup</h3><p><strong>Master Role</strong> <strong>— Identity for control plane nodes</strong></p><p>This creates an <strong>IAM role</strong> that EC2 instances can assume to get AWS permissions. The assume_role_policy is a <strong>trust policy</strong> that says “only EC2 instances can use this role” – it controls WHO can assume the role, not WHAT they can do. The actual permissions (like accessing S3 or Parameter Store) are added later by attaching separate IAM policies to this role.</p><pre># modules/iam/main.tf<br><br>resource &quot;aws_iam_role&quot; &quot;kubernetes_master&quot; {<br>  name = &quot;kubernetes-master-role&quot;<br><br>  assume_role_policy = jsonencode({<br>    Version = &quot;2012-10-17&quot;<br>    Statement = [<br>      {<br>        Action = &quot;sts:AssumeRole&quot;<br>        Effect = &quot;Allow&quot;<br>        Principal = {<br>          Service = &quot;ec2.amazonaws.com&quot;<br>        }<br>      }<br>    ]<br>  })<br><br>  tags = {<br>    Name        = &quot;${terraform.workspace} - Kubernetes Master Role&quot;<br>    Description = &quot;IAM role for Kubernetes control plane nodes with AWS API permissions&quot;<br>    Purpose     = &quot;Kubernetes Control Plane&quot;<br>    Environment = terraform.workspace<br>    ManagedBy   = &quot;Terraform&quot;<br>    Project     = &quot;Kubernetes&quot;<br>    NodeType    = &quot;Control Plane&quot;<br>    Service     = &quot;EC2&quot;<br>  }<br>}</pre><p><strong>Master Instance Profile — Attaches role to EC2</strong>.</p><p>This creates an <strong>IAM instance profile</strong> that acts as a bridge between EC2 instances and IAM roles. The instance profile gets attached to EC2 instances and allows them to assume the specified IAM role to obtain temporary AWS credentials. Think of it as the mechanism that lets EC2 instances “wear” the IAM role — without an instance profile, EC2 instances cannot access AWS APIs because they have no way to authenticate or assume roles.</p><pre># modules/iam/main.tf<br><br>resource &quot;aws_iam_instance_profile&quot; &quot;kubernetes_master&quot; {<br>  name = &quot;kubernetes-master-profile-${random_id.cluster.hex}&quot; <br>  role = aws_iam_role.kubernetes_master.name<br><br>  tags = {<br>    Name        = &quot;${terraform.workspace} - Kubernetes Control Plane Instance Profile&quot;<br>    Description = &quot;Instance profile for control plane nodes - enables AWS API access for cluster management&quot;<br>    Purpose     = &quot;Kubernetes Control Plane&quot;<br>    Environment = terraform.workspace<br>    ManagedBy   = &quot;Terraform&quot;<br>  }<br>}</pre><p><strong>Master SSM Policy — Parameter store permissions</strong></p><p>This policy gives the <strong>control plane nodes permission to store and manage cluster secrets</strong> in AWS Parameter Store. When the first control plane node sets up the cluster, it creates a “join command” (like a password) and stores it in AWS Parameter Store so other nodes can retrieve it and join the cluster. The policy restricts access to only parameters that start with /k8s/ for security.</p><p>What control plane can do:</p><ul><li><strong>PutParameter:</strong> Store cluster join command and tokens</li><li><strong>GetParameter:</strong> Read existing cluster info</li><li><strong>DeleteParameter:</strong> Clean up old/expired tokens</li><li><strong>DescribeParameters:</strong> List available parameters</li></ul><pre># modules/iam/main.tf<br><br># SSM parameter access policy for Kubernetes control plane - allows storing/retrieving cluster join tokens<br>resource &quot;aws_iam_role_policy&quot; &quot;kubernetes_master_ssm&quot; {<br>  name = &quot;kubernetes-master-ssm-policy&quot;<br>  role = aws_iam_role.kubernetes_master.id<br>  <br>  policy = jsonencode({<br>    # Policy grants control plane full access to SSM parameters under /k8s/ namespace<br>    Version = &quot;2012-10-17&quot;<br>    Statement = [<br>      {<br>        Effect = &quot;Allow&quot;<br>        Action = [<br>          &quot;ssm:PutParameter&quot;,     # Store cluster join command with tokens and CA cert hash<br>          &quot;ssm:GetParameter&quot;,     # Retrieve existing parameters for validation<br>          &quot;ssm:DeleteParameter&quot;,  # Clean up expired or invalid join tokens<br>          &quot;ssm:DescribeParameters&quot; # List and discover available k8s parameters<br>        ]<br>        # Restrict access to only k8s namespace parameters for security<br>        Resource = &quot;arn:aws:ssm:us-east-1:${data.aws_caller_identity.current.account_id}:parameter/k8s/*&quot;<br>      }<br>    ]<br>  })<br>}</pre><h3>Worker Nodes IAM Setup</h3><p><strong>Worker Role — Identity for worker nodes</strong></p><p>This creates an <strong>IAM role</strong> specifically for worker node EC2 instances. The assume_role_policy is a <strong>trust policy</strong> that allows only EC2 instances to assume this role and get AWS credentials. This role will later have policies attached that give worker nodes the specific permissions they need (like pulling container images, managing storage volumes, and handling pod networking) – but this just creates the empty role container that worker nodes can use.</p><pre># modules/iam/main.tf<br><br>resource &quot;aws_iam_role&quot; &quot;kubernetes_worker&quot; {<br>  name = &quot;kubernetes-worker-profile-${random_id.cluster.hex}&quot;<br><br>  assume_role_policy = jsonencode({<br>    Version = &quot;2012-10-17&quot;<br>    Statement = [<br>      {<br>        Action = &quot;sts:AssumeRole&quot;<br>        Effect = &quot;Allow&quot;<br>        Principal = {<br>          Service = &quot;ec2.amazonaws.com&quot;<br>        }<br>      }<br>    ]<br>  })<br><br>  tags = {<br>    Name        = &quot;${terraform.workspace} - Kubernetes Worker Role&quot;<br>    Description = &quot;IAM role for Kubernetes worker nodes with permissions for pod networking, storage, and container operations&quot;<br>    Purpose     = &quot;Kubernetes Worker Nodes&quot;<br>    Environment = terraform.workspace<br>    ManagedBy   = &quot;Terraform&quot;<br>  }<br>}</pre><p><strong>Worker Instance Profile — Attaches role to EC2</strong></p><p>This creates an <strong>IAM instance profile</strong> that acts as a bridge between worker node EC2 instances and the worker IAM role. The instance profile gets attached to worker EC2 instances and allows them to assume the kubernetes_worker role to obtain AWS credentials. This enables worker nodes to access AWS APIs for tasks like pulling container images, managing EBS volumes, and configuring networking – without it, worker nodes couldn’t authenticate with AWS services.</p><pre># modules/iam/main.tf<br><br>resource &quot;aws_iam_instance_profile&quot; &quot;kubernetes_worker&quot; {<br>  name = &quot;kubernetes-worker-profile&quot;<br>  role = aws_iam_role.kubernetes_worker.name<br><br>  tags = {<br>    Name        = &quot;${terraform.workspace} - Kubernetes Worker Instance Profile&quot;<br>    Description = &quot;Instance profile for worker nodes - enables AWS API access for container operations and networking&quot;<br>    Purpose     = &quot;Kubernetes Worker Nodes&quot;<br>    Environment = terraform.workspace<br>    ManagedBy   = &quot;Terraform&quot;<br>  }<br>}</pre><p><strong>Worker SSM Policy — Read-only parameter access</strong></p><p>This creates an <strong>IAM policy</strong> that gets attached to the worker role, giving worker nodes <strong>read-only access</strong> to AWS Parameter Store. It allows worker nodes to retrieve the cluster join command that was stored by the control plane, but restricts access to only parameters under the /k8s/ path for security. This is how worker nodes get the secret tokens they need to join the existing Kubernetes cluster.</p><pre># modules/iam/main.tf<br><br># Worker node SSM access - read-only permissions to get cluster join command<br>resource &quot;aws_iam_role_policy&quot; &quot;kubernetes_worker_ssm&quot; {<br>  name = &quot;kubernetes-worker-ssm-policy&quot;<br>  role = aws_iam_role.kubernetes_worker.id<br>  <br>  policy = jsonencode({<br>    # Policy allows worker nodes to read SSM parameters under /k8s/ path<br>    Version = &quot;2012-10-17&quot;<br>    Statement = [<br>      {<br>        Effect = &quot;Allow&quot;<br>        Action = [<br>          &quot;ssm:GetParameter&quot;,   # Read join command stored by control plane<br>          &quot;ssm:GetParameters&quot;   # Batch read multiple parameters if needed<br>        ]<br>        # Only allow access to k8s namespace parameters<br>        Resource = &quot;arn:aws:ssm:us-east-1:${data.aws_caller_identity.current.account_id}:parameter/k8s/*&quot;<br>      }<br>    ]<br>  })<br>}</pre><h3>How IAM works:</h3><ol><li>Control plane starts -&gt; Gets master role</li><li>Kubernetes initializes -&gt; Generates join token</li><li>Control plane stores join command in SSM parameter /k8s/join-command</li><li>Worker nodes start -&gt; Get worker role</li><li>Workers read join command from SSM</li><li>Workers join the cluster using the token</li></ol><p>Expose the return values to be used in other modules.</p><pre># modules/iam/outputs.tf<br><br>output &quot;kubernetes_master_instance_profile&quot; {  <br>  description = &quot;IAM instance profile name for Kubernetes control plane nodes - provides AWS API permissions&quot;<br>  value       = aws_iam_instance_profile.kubernetes_master.name<br>}<br><br>output &quot;kubernetes_worker_instance_profile&quot; {  <br>  description = &quot;IAM instance profile name for Kubernetes worker nodes - provides AWS API permissions for pods and services&quot;<br>  value       = aws_iam_instance_profile.kubernetes_worker.name<br>}</pre><p>Create a custom module named <strong>iam</strong>.</p><pre># environments/development/main.tf<br><br>module iam {<br>  source = &quot;../../modules/iam&quot;<br>}</pre><h3>SSM Parameter Store</h3><p>This SSM parameter provides a secure, automated way for control plane nodes to share fresh join tokens with worker nodes, eliminating manual steps and security risks. You don’t go to every node and ssh just to enter the join command.</p><pre># modules/ssm/main.tf<br>resource &quot;aws_ssm_parameter&quot; &quot;join_command&quot; {<br>  name        = &quot;/k8s/control-plane/join-command&quot;<br>  type        = &quot;SecureString&quot;<br>  value       = &quot;placeholder-will-be-updated-by-script&quot;<br>  description = &quot;Kubernetes cluster join command for worker nodes - automatically updated by control plane initialization script&quot;<br>  <br>  lifecycle {<br>    ignore_changes = [value] # Let the script update the value<br>  }<br>}</pre><p><strong>Name &amp; Path:</strong></p><pre>name = &quot;/k8s/control-plane/join-command&quot;</pre><ul><li><strong>Hierarchical path:</strong> Organized under /k8s/ namespace</li><li><strong>Specific location:</strong> Control plane section for join commands</li><li><strong>Matches IAM policy:</strong> IAM roles above have access to /k8s/* path</li></ul><p><strong>Security Type:</strong></p><pre>type = &quot;SecureString&quot;</pre><ul><li><strong>Encrypted storage:</strong> Value is encrypted at rest in AWS</li><li><strong>Secure transmission:</strong> Encrypted in transit when accessed</li><li><strong>Better than plaintext:</strong> Protects sensitive cluster tokens</li></ul><p><strong>The Join Command Content</strong><br>What gets stored (after control plane runs):</p><pre># Real example of what replaces the placeholder:<br>&quot;kubeadm join 10.0.1.10:6443 --token abc123.def456ghi789 --discovery-token-ca-cert-hash sha256:1234567890abcdef...&quot;</pre><p><strong>Why Ignore Changes is needed</strong>?</p><ul><li><strong>Control plane script</strong> updates the value with real join command</li><li><strong>Without lifecycle:</strong> Terraform would overwrite script’s value back to placeholder</li><li><strong>With lifecycle:</strong> Terraform ignores value changes, lets script manage it</li></ul><pre>lifecycle {<br>  ignore_changes = [value] # Let the script update the value<br>}</pre><p>Create a custom module named <strong>ssm.</strong></p><pre># environments/development/main.tf<br><br>module ssm {<br>  source = &quot;../../modules/ssm&quot;<br>}</pre><h3>Networking</h3><p>The networking section creates the <strong>foundational network infrastructure</strong> for the Kubernetes cluster.</p><p>Create the variables first.</p><pre># modules/networking/variables.tf<br><br>variable &quot;aws_region&quot; {<br>  type        = map(string)<br>  description = &quot;AWS region for each environment - maps workspace to region&quot;<br>  default = {<br>    &quot;development&quot; = &quot;us-east-1&quot;<br>    &quot;production&quot;  = &quot;us-east-2&quot;<br>  }<br>}<br><br>variable &quot;public_subnet_cidrs&quot; {<br>  type        = list(string)<br>  description = &quot;Public Subnet CIDR values for load balancers and internet-facing resources&quot;<br>  default     = [&quot;10.0.1.0/24&quot;]<br>}<br><br>variable &quot;private_subnet_cidrs&quot; {<br>  type        = list(string)<br>  description = &quot;Private Subnet CIDR values for Kubernetes nodes and internal services&quot;<br>  default     = [&quot;10.0.2.0/24&quot;, &quot;10.0.3.0/24&quot;, &quot;10.0.4.0/24&quot;, &quot;10.0.5.0/24&quot;, &quot;10.0.6.0/24&quot;]<br>}<br><br>variable &quot;azs&quot; {<br>  type        = map(list(string))<br>  description = &quot;Availability Zones for each environment - ensures high availability across multiple AZs&quot;<br>  default = {<br>    &quot;development&quot; = [&quot;us-east-1a&quot;, &quot;us-east-1b&quot;, &quot;us-east-1c&quot;, &quot;us-east-1d&quot;, &quot;us-east-1f&quot;]<br>    &quot;production&quot;  = [&quot;us-east-2a&quot;, &quot;us-east-2b&quot;, &quot;us-east-2c&quot;, &quot;us-east-2d&quot;, &quot;us-east-2f&quot;]<br>  }<br>}</pre><p><strong>VPC (Virtual Private Cloud)</strong></p><p>A VPC (Virtual Private Cloud) in Amazon Web Services (AWS) is your own isolated network within the AWS cloud — like a private data center you control.</p><pre># modules/networking/main.tf<br><br>resource &quot;aws_vpc&quot; &quot;main&quot; {<br>  cidr_block            = &quot;10.0.0.0/16&quot;<br>  enable_dns_hostnames  = true<br>  enable_dns_support    = true<br>  <br>  tags = {<br>    Name                = &quot;${terraform.workspace} - Kubernetes Cluster VPC&quot;<br>    Environment         = terraform.workspace<br>    Purpose             = &quot;Kubernetes Infrastructure&quot;<br>  }<br>}</pre><p><strong>Public Subnet (Internet-facing)</strong></p><p>A public subnet in AWS is a subnet inside a VPC that can directly communicate with the internet — typically used for resources that need to be accessible from outside AWS</p><p>In AWS, a DMZ (Demilitarized Zone) is a subnet or network segment that acts as a buffer zone between the public internet and your private/internal AWS resources. It’s used to host public-facing services while minimizing the exposure of your internal network.</p><p>The public subnet contains the <strong>bastion host</strong> — a dedicated EC2 instance that acts as a secure gateway for accessing private resources. The bastion has a public IP and sits in the public subnet, allowing administrators to SSH into it from the internet, then use it as a stepping stone to securely connect to instances in private subnets that don’t have direct internet access.</p><pre># modules/networking/main.tf<br><br>resource &quot;aws_subnet&quot; &quot;public_subnets&quot; {<br>  count               = length(var.public_subnet_cidrs)<br>  vpc_id              = aws_vpc.main.id<br>  cidr_block          = element(var.public_subnet_cidrs, count.index)<br>  availability_zone   = element(var.azs[terraform.workspace], count.index)<br><br>  tags = {<br>    Name              = &quot;${terraform.workspace} - Public Subnet ${count.index + 1}&quot;<br>    Description       = &quot;Public subnet for bastion host and load balancers&quot;<br>    Type              = &quot;Public&quot;<br>    Environment       = terraform.workspace<br>    AvailabilityZone  = element(var.azs[terraform.workspace], count.index)<br>    Purpose           = &quot;DMZ&quot;<br>    ManagedBy         = &quot;Terraform&quot;<br>    Project           = &quot;Kubernetes&quot;<br>    Tier              = &quot;DMZ&quot;  # Demilitarized Zone<br>  }<br>}</pre><p><strong>Private Subnets (Internal)</strong></p><p>A private subnet in AWS is a subnet within your VPC that does NOT have direct access to or from the public internet. It’s used to host internal resources that should remain isolated from external access, such as: Application servers, Databases (e.g., RDS), Internal services (e.g., Redis, internal APIs).</p><p>Hosts the Kubernetes control plane and worker nodes. No direct internet access (protected from external access).</p><pre># modules/networking/main.tf<br><br>resource &quot;aws_subnet&quot; &quot;private_subnets&quot; {<br>  count               = min(length(var.private_subnet_cidrs), length(var.azs[terraform.workspace]))<br>  vpc_id              = aws_vpc.main.id<br>  cidr_block          = var.private_subnet_cidrs[count.index]<br>  availability_zone   = var.azs[terraform.workspace][count.index] # Ensures 1 AZ per subnet<br><br>  tags = {<br>    Name              = &quot;${terraform.workspace} - Private Subnet ${count.index + 1}&quot;<br>    Description       = &quot;Private subnet for Kubernetes worker and control plane nodes&quot;<br>    Type              = &quot;Private&quot;<br>    Environment       = terraform.workspace<br>    AvailabilityZone  = var.azs[terraform.workspace][count.index]<br>    Purpose           = &quot;Kubernetes Nodes&quot;<br>    ManagedBy         = &quot;Terraform&quot;<br>    Project           = &quot;Kubernetes&quot;<br>    Tier              = &quot;Internal&quot;<br>  }<br>}</pre><p>Multi-AZ Distribution: Spreads resources across multiple data centers (High availability). If one AZ fails, others continue running (Fault tolerance).</p><pre>availability_zone = element(var.azs[terraform.workspace], count.index)</pre><p><strong>Internet Gateway</strong></p><p>An Internet Gateway (IGW) in AWS is a component that connects your VPC to the internet. It allows resources in your VPC (like EC2 instances in a public subnet) to send traffic to the internet and receive traffic from the internet. It is attached to the public subnet. It enables bastion host to receive ssh connections. It handles:</p><ul><li><strong>Outbound connections</strong> (e.g., your EC2 instance accessing a website)</li><li><strong>Inbound connections</strong> (e.g., users accessing your public web server)</li></ul><pre># modules/networking/main.tf<br><br>resource &quot;aws_internet_gateway&quot; &quot;igw&quot; {<br>  vpc_id = aws_vpc.main.id<br><br>  tags = {<br>    Name        = &quot;${terraform.workspace} - Internet Gateway&quot;<br>    Purpose     = &quot;Internet access for public subnets&quot;<br>    Description = &quot;Provides internet connectivity for bastion host and load balancers&quot;<br>    Type        = &quot;Gateway&quot;<br>  }<br>}</pre><p><strong>Public route table</strong></p><p>A public route table in AWS is a route table associated with one or more public subnets, and it directs traffic destined for the internet to an Internet Gateway (IGW).</p><p><strong>Traffic flow</strong>: Public subnet -&gt; Internet Gateway -&gt; Internet</p><pre># modules/networking/main.tf<br><br>resource &quot;aws_route_table&quot; &quot;public&quot; {<br>  vpc_id = aws_vpc.main.id<br>  <br>  tags = {<br>    Name        = &quot;${terraform.workspace} - Public Route Table&quot;<br>    Description = &quot;Route table for public subnets - directs traffic to internet gateway&quot;<br>    Type        = &quot;Public&quot;<br>    Purpose     = &quot;Internet routing for DMZ resources&quot;<br>    Environment = terraform.workspace<br>    ManagedBy   = &quot;Terraform&quot;<br>    Tier        = &quot;DMZ&quot;<br>    RouteType   = &quot;Internet-bound&quot;<br>    Project     = &quot;Kubernetes&quot;<br>  }<br>}</pre><p><strong>Private Route Table</strong></p><p>A private route table in AWS is a route table used by private subnets — subnets that do not have direct access to or from the internet.</p><p>A private route table does NOT have a route to an Internet Gateway (IGW). Instead, it may have a route to a NAT Gateway or no external route at all, depending on whether you want outbound internet access (e.g., for software updates) or complete isolation.</p><p>Traffic flow: Private subnet -&gt; NAT Gateway -&gt; Internet Gateway -&gt; Internet</p><pre># modules/networking/main.tf<br><br>resource &quot;aws_route_table&quot; &quot;private&quot; {<br>  vpc_id = aws_vpc.main.id<br><br>  tags = {<br>    Name        = &quot;${terraform.workspace} - Private Route Table&quot;<br>    Description = &quot;Route table for private subnets - directs internet traffic through NAT Gateway&quot;<br>    Type        = &quot;Private&quot;<br>    Environment = terraform.workspace<br>    Purpose     = &quot;NAT Gateway Routing&quot;<br>    ManagedBy   = &quot;Terraform&quot;<br>  }<br>}</pre><p><strong>Elastic IP for NAT Gateway</strong></p><p>Static public IP provides consistent IP address and it is required for NAT Gateway operation.</p><p>What NAT Gateway Does:</p><pre>Private Subnet (10.0.1.x) -&gt; NAT Gateway -&gt; Internet</pre><p>It translates private IPs to public IP for outbound traffic. It needs public IP to communicate with internet on behalf of private resources. Without EIP — NAT Gateway Won’t Work.</p><p>Without EIP (Dynamic IP):</p><pre>Today: NAT uses IP 12.123.45.67<br>Tomorrow: AWS changes it to 12.234.56.78<br>Result: External services block your new IP</pre><p>With EIP (Static IP):</p><pre>Always: NAT uses IP 54.123.45.67<br>Result: Consistent external identity</pre><pre># modules/networking/main.tf<br><br>resource &quot;aws_eip&quot; &quot;nat_eip&quot; {<br>  domain = &quot;vpc&quot;<br><br>  tags = {<br>    Name        = &quot;${terraform.workspace} - NAT Gateway EIP&quot;<br>    Description = &quot;Elastic IP for NAT Gateway - enables internet access for private subnets&quot;<br>    Purpose     = &quot;NAT Gateway&quot;<br>    Environment = terraform.workspace<br>    ManagedBy   = &quot;Terraform&quot;<br>  }<br>}</pre><p><strong>NAT Gateway</strong></p><p>Allows private subnets to reach internet. Outbound traffic only, no inbound from internet. It’s essential for Kubernetes nodes to download images, updates and etc.</p><pre># modules/networking/main.tf<br><br>resource &quot;aws_nat_gateway&quot; &quot;nat&quot; {<br>  allocation_id = aws_eip.nat_eip.id<br>  subnet_id     = aws_subnet.public_subnets[0].id<br><br>  tags = {<br>    Name        = &quot;${terraform.workspace} - NAT Gateway&quot;<br>    Description = &quot;NAT Gateway for private subnet internet access - enables Kubernetes nodes to reach external services&quot;<br>    Purpose     = &quot;Private Subnet Internet Access&quot;<br>    Environment = terraform.workspace<br>    ManagedBy   = &quot;Terraform&quot;<br>  }<br><br>  depends_on = [aws_internet_gateway.igw]<br>}</pre><p><strong>Add a default route to the internet gateway in the public route table</strong></p><pre># modules/networking/main.tf<br><br>resource &quot;aws_route&quot; &quot;public_internet_access&quot; {<br>  route_table_id         = aws_route_table.public.id<br>  destination_cidr_block = &quot;0.0.0.0/0&quot;<br>  gateway_id             = aws_internet_gateway.igw.id<br>}</pre><p><strong>Associate only the first public subnet with the public route table</strong></p><pre># modules/networking/main.tf<br><br>resource &quot;aws_route_table_association&quot; &quot;public_first_subnet&quot; {<br>  subnet_id      = aws_subnet.public_subnets[0].id<br>  route_table_id = aws_route_table.public.id<br>}</pre><p><strong>Add a route in the private route table to direct internet traffic through the NAT Gateway</strong></p><pre># modules/networking/main.tf<br><br>resource &quot;aws_route&quot; &quot;private_nat&quot; {<br>  route_table_id         = aws_route_table.private.id<br>  destination_cidr_block = &quot;0.0.0.0/0&quot;<br>  nat_gateway_id         = aws_nat_gateway.nat.id<br>}</pre><p><strong>Link private subnets to the private route table</strong></p><pre># modules/networking/main.tf<br><br>resource &quot;aws_route_table_association&quot; &quot;private&quot; {<br>  count          = length(var.private_subnet_cidrs)<br>  subnet_id      = element(aws_subnet.private_subnets[*].id, count.index)<br>  route_table_id = aws_route_table.private.id<br>}</pre><p>Expose the return values to be used in other modules.</p><pre># modules/networking/outputs.tf<br><br>output &quot;vpc_id&quot; {<br>  description = &quot;ID of the VPC for the Kubernetes cluster&quot;<br>  value       = aws_vpc.main.id<br>}<br><br>output &quot;vpc_cidr_block&quot; {<br>  description = &quot;CIDR block of the VPC for security group rules and network configuration&quot;<br>  value       = aws_vpc.main.cidr_block<br>}<br><br>output &quot;private_subnets&quot; {<br>  description = &quot;Private subnets for Kubernetes worker nodes and internal services&quot;<br>  value       = aws_subnet.private_subnets<br>}<br><br>output &quot;public_subnets&quot; {<br>  description = &quot;Public subnets for load balancers, bastion hosts, and internet-facing resources&quot;<br>  value       = aws_subnet.public_subnets<br>}</pre><p>Create a custom module named <strong>networking</strong>.</p><pre># environments/development/main.tf<br><br>module networking {<br>  source = &quot;../../modules/networking&quot;<br>}</pre><h3>Security Groups</h3><p>The security groups creates network security rules (firewalls) for Kubernetes cluster. This creates a <strong>secure, layered defense</strong> where each Kubernetes component can communicate as needed while preventing unauthorized access from the internet.</p><p>To know more about Kubernetes Ports and Protocols, visit <a href="https://kubernetes.io/docs/reference/networking/ports-and-protocols/">https://kubernetes.io/docs/reference/networking/ports-and-protocols/</a>.</p><p>Create the variables.</p><pre># modules/security_groups/variables.tf<br><br>// FROM Other Module<br>variable &quot;vpc_id&quot; {<br>  description = &quot;VPC ID from AWS module&quot;<br>  type        = string<br>}<br><br>variable &quot;vpc_cidr_block&quot; {<br>  description = &quot;CIDR block of the VPC for internal network communication&quot;<br>  type        = string<br>}</pre><ol><li><strong>Bastion Security Group</strong>: Creates a firewall group for the bastion host.</li></ol><pre># modules/security_groups/main.tf<br><br>resource &quot;aws_security_group&quot; &quot;bastion&quot; {<br>  name        = &quot;bastion-sg&quot; <br>  vpc_id      = var.vpc_id<br>  description = &quot;Security group for the bastion host&quot;<br><br>  tags = {<br>    Name = &quot;${terraform.workspace} - Bastion Host SG&quot;<br>  }<br>}</pre><p>2. <strong>Bastion SSH from Internet</strong>: Allows SSH connections to bastion host from anywhere on the internet.</p><pre># modules/security_groups/main.tf<br><br>resource &quot;aws_vpc_security_group_ingress_rule&quot; &quot;bastion_ssh_anywhere&quot; {<br>  security_group_id = aws_security_group.bastion.id<br>  from_port         = 22<br>  to_port           = 22<br>  ip_protocol       = &quot;tcp&quot;<br>  cidr_ipv4         = &quot;0.0.0.0/0&quot;<br>  description       = &quot;Allow SSH access to bastion host from any IP address&quot;<br><br>  tags = {<br>    Name = &quot;${terraform.workspace} - Bastion SSH Internet Access&quot;<br>  }<br>}</pre><p>3. <strong>Bastion SSH to Control Plane</strong>: Allows bastion host to SSH to control plane nodes.</p><pre># modules/security_groups/main.tf<br><br>resource &quot;aws_vpc_security_group_egress_rule&quot; &quot;bastion_egress_control_plane&quot; {<br>  security_group_id             = aws_security_group.bastion.id<br>  from_port                     = 22<br>  to_port                       = 22<br>  ip_protocol                   = &quot;tcp&quot;<br>  referenced_security_group_id  = aws_security_group.control_plane.id<br>  description = &quot;Allow SSH from bastion host to Kubernetes control plane nodes for cluster administration&quot;<br><br>  tags = {<br>    Name = &quot;${terraform.workspace} - Bastion SSH to Control Plane&quot;<br>  }<br>}</pre><p>4. <strong>Bastion SSH to Workers</strong>: Allows bastion host to SSH to worker nodes.</p><pre># modules/security_groups/main.tf<br><br>resource &quot;aws_vpc_security_group_egress_rule&quot; &quot;bastion_egress_workers&quot; {<br>  security_group_id             = aws_security_group.bastion.id<br>  from_port                     = 22<br>  to_port                       = 22<br>  ip_protocol                   = &quot;tcp&quot;<br>  referenced_security_group_id  = aws_security_group.worker_node.id<br>  description                   = &quot;Allow SSH from bastion host to worker nodes for maintenance and troubleshooting&quot;<br>  <br>  tags = {<br>    Name = &quot;${terraform.workspace} - Bastion SSH to Worker Nodes&quot;<br>  }<br>}</pre><p>5. <strong>Control Plane Security Grou</strong>p: Creates a firewall group for Kubernetes master nodes.</p><pre># modules/security_groups/main.tf<br><br>resource &quot;aws_security_group&quot; &quot;control_plane&quot; {<br>  name        = &quot;control-plane-sg&quot;  <br>  vpc_id      = var.vpc_id<br>  description = &quot;Security group for the Kubernetes control plane&quot;<br><br>  tags = {<br>    Name = &quot;${terraform.workspace} - Kubernetes Control Plane SG&quot;<br>  }<br>}</pre><p>6. <strong>Control Plane SSH Access</strong>: Allows SSH to control plane from bastion host only.</p><pre># modules/security_groups/main.tf<br><br>resource &quot;aws_vpc_security_group_ingress_rule&quot; &quot;control_plane_ssh&quot; {<br>  security_group_id             = aws_security_group.control_plane.id<br>  from_port                     = 22<br>  to_port                       = 22<br>  ip_protocol                   = &quot;tcp&quot;<br>  referenced_security_group_id  = aws_security_group.bastion.id<br>  description                   = &quot;Allow SSH access to control plane nodes from bastion host for cluster administration&quot;<br><br>  tags = {<br>    Name = &quot;${terraform.workspace} - Control Plane SSH from Bastion&quot;<br>  }<br>}</pre><p>7. <strong>Control Plane etcd</strong>: Allows etcd database communication. Kubernetes stores all data in etcd.</p><pre># modules/security_groups/main.tf<br><br>resource &quot;aws_vpc_security_group_ingress_rule&quot; &quot;control_plane_etcd&quot; {<br>  security_group_id = aws_security_group.control_plane.id<br>  from_port         = 2379<br>  to_port           = 2380<br>  ip_protocol       = &quot;tcp&quot;<br>  cidr_ipv4         = var.vpc_cidr_block<br>  description       = &quot;Allow etcd client and peer communication within VPC for Kubernetes cluster state management&quot;<br><br>  tags = {<br>    Name = &quot;${terraform.workspace} - Control Plane etcd Communication&quot;<br>  }<br>}</pre><p>8. <strong>Control Plane kubelet</strong>: Allows kubelet API access. Use for monitoring and managing pods.</p><pre># modules/security_groups/main.tf<br><br>resource &quot;aws_vpc_security_group_ingress_rule&quot; &quot;control_plane_self_control_plane&quot; {<br>  security_group_id = aws_security_group.control_plane.id<br>  from_port         = 10250<br>  to_port           = 10250<br>  ip_protocol       = &quot;tcp&quot;<br>  cidr_ipv4         = var.vpc_cidr_block<br>  description       = &quot;Allow kubelet API access within VPC for control plane node communication and monitoring&quot;<br><br>  tags = {<br>    Name = &quot;${terraform.workspace} - Control Plane kubelet API&quot;<br>  }<br>}</pre><p>9. <strong>Control Plane Scheduler</strong>: Allows access to scheduler metrics. Use for health checks and monitoring.</p><pre># modules/security_groups/main.tf<br><br>resource &quot;aws_vpc_security_group_ingress_rule&quot; &quot;control_plane_kube_scheduler&quot; {<br>  security_group_id             = aws_security_group.control_plane.id<br>  from_port                     = 10259<br>  to_port                       = 10259<br>  ip_protocol                   = &quot;tcp&quot;<br>  cidr_ipv4                     = var.vpc_cidr_block<br>  description                   = &quot;Allow kube-scheduler metrics and health check access from VPC for cluster monitoring&quot;<br><br>  tags = {<br>    Name = &quot;${terraform.workspace} - Control Plane kube-scheduler&quot;<br>  }<br>}</pre><p>It’s a bit lengthy. <a href="https://rinavillaruz.com/2025/08/04/deploy-kubernetes-using-terraform-and-aws/">You can view the full tutorial here.</a> :)</p><ul><li><a href="https://rinavillaruz.com/2025/08/04/deploy-kubernetes-using-terraform-and-aws/">Deploy Kubernetes using Terraform and Amazon Web Services (AWS)</a></li><li><a href="https://github.com/rinavillaruz/terraform-aws-kubernetes/">GitHub - rinavillaruz/terraform-aws-kubernetes: Terraform scripts for provisioning a production-grade Kubernetes cluster on AWS using Terraform modules and Debian 12 / Ubuntu. This project automates the setup of Kubernetes infrastructure, including VPC, subnets, ec2, and more</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f9987b4a04df" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Building Better Containers: Docker Multi-Stage Builds]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/@rinavillaruz/building-better-containers-docker-multi-stage-builds-e1dbb3da961e?source=rss-2f420b86b694------2"><img src="https://cdn-images-1.medium.com/max/1920/1*2Yz4AlqoA62YmOLp7yhZ7w.jpeg" width="1920"></a></p><p class="medium-feed-snippet">There are several factors why multi-stage builds can help build better containers.</p><p class="medium-feed-link"><a href="https://medium.com/@rinavillaruz/building-better-containers-docker-multi-stage-builds-e1dbb3da961e?source=rss-2f420b86b694------2">Continue reading on Medium »</a></p></div>]]></description>
            <link>https://medium.com/@rinavillaruz/building-better-containers-docker-multi-stage-builds-e1dbb3da961e?source=rss-2f420b86b694------2</link>
            <guid isPermaLink="false">https://medium.com/p/e1dbb3da961e</guid>
            <category><![CDATA[linux]]></category>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[containerization]]></category>
            <dc:creator><![CDATA[Rina Villaruz]]></dc:creator>
            <pubDate>Tue, 21 Jan 2025 21:00:27 GMT</pubDate>
            <atom:updated>2025-01-21T21:00:27.593Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Debugging Vagrant 2.4.3 Installation Failures on Ubuntu 22.04 Jammy Jellyfish with VirtualBox 7.14]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/@rinavillaruz/debugging-vagrant-2-4-3-installation-failures-on-ubuntu-22-04-jammy-jellyfish-with-virtualbox-7-14-8433e9be2a1d?source=rss-2f420b86b694------2"><img src="https://cdn-images-1.medium.com/max/1980/1*tG_XDpFSduZ28SQJieHH9A.jpeg" width="1980"></a></p><p class="medium-feed-snippet">Vagrant and VirtualBox on Mac M2 Max are a no-no for now because of the arm architecture, which is not fully available yet. So, I decided&#x2026;</p><p class="medium-feed-link"><a href="https://medium.com/@rinavillaruz/debugging-vagrant-2-4-3-installation-failures-on-ubuntu-22-04-jammy-jellyfish-with-virtualbox-7-14-8433e9be2a1d?source=rss-2f420b86b694------2">Continue reading on Medium »</a></p></div>]]></description>
            <link>https://medium.com/@rinavillaruz/debugging-vagrant-2-4-3-installation-failures-on-ubuntu-22-04-jammy-jellyfish-with-virtualbox-7-14-8433e9be2a1d?source=rss-2f420b86b694------2</link>
            <guid isPermaLink="false">https://medium.com/p/8433e9be2a1d</guid>
            <category><![CDATA[ubuntu]]></category>
            <category><![CDATA[vagrant]]></category>
            <category><![CDATA[virtualbox]]></category>
            <dc:creator><![CDATA[Rina Villaruz]]></dc:creator>
            <pubDate>Fri, 10 Jan 2025 15:26:50 GMT</pubDate>
            <atom:updated>2025-01-10T15:26:50.011Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Upgrading Docker from version 25 to version 27 in Amazon Linux 2023.6.20241212]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/@rinavillaruz/upgrading-docker-from-version-25-to-version-27-in-amazon-linux-2023-6-20241212-c8b64b228c8a?source=rss-2f420b86b694------2"><img src="https://cdn-images-1.medium.com/max/1920/1*BxXUWhl4fZrvv1Pwwp2icA.jpeg" width="1920"></a></p><p class="medium-feed-snippet">I have this Jenkins server that I haven&#x2019;t touched for a year, and when I started it, I was greeted with two upgrades. One is from Jenkins&#x2026;</p><p class="medium-feed-link"><a href="https://medium.com/@rinavillaruz/upgrading-docker-from-version-25-to-version-27-in-amazon-linux-2023-6-20241212-c8b64b228c8a?source=rss-2f420b86b694------2">Continue reading on Medium »</a></p></div>]]></description>
            <link>https://medium.com/@rinavillaruz/upgrading-docker-from-version-25-to-version-27-in-amazon-linux-2023-6-20241212-c8b64b228c8a?source=rss-2f420b86b694------2</link>
            <guid isPermaLink="false">https://medium.com/p/c8b64b228c8a</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[linux]]></category>
            <dc:creator><![CDATA[Rina Villaruz]]></dc:creator>
            <pubDate>Wed, 08 Jan 2025 12:43:10 GMT</pubDate>
            <atom:updated>2025-01-08T12:43:10.231Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Automate Background Tasks with Linux Service and Timer]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/@rinavillaruz/automate-background-tasks-with-linux-service-and-timer-472fd425fefa?source=rss-2f420b86b694------2"><img src="https://cdn-images-1.medium.com/max/1920/1*NNTpv69yP1WQnlJ1zmiSrw.jpeg" width="1920"></a></p><p class="medium-feed-snippet">What is a Linux Service? It is a program that runs in the background without user interaction. It usually stays running even after a&#x2026;</p><p class="medium-feed-link"><a href="https://medium.com/@rinavillaruz/automate-background-tasks-with-linux-service-and-timer-472fd425fefa?source=rss-2f420b86b694------2">Continue reading on Medium »</a></p></div>]]></description>
            <link>https://medium.com/@rinavillaruz/automate-background-tasks-with-linux-service-and-timer-472fd425fefa?source=rss-2f420b86b694------2</link>
            <guid isPermaLink="false">https://medium.com/p/472fd425fefa</guid>
            <category><![CDATA[linux-service]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[linux]]></category>
            <dc:creator><![CDATA[Rina Villaruz]]></dc:creator>
            <pubDate>Mon, 09 Dec 2024 13:26:07 GMT</pubDate>
            <atom:updated>2024-12-09T13:26:07.021Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[EBS Volume Resizing in AWS]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/@rinavillaruz/ebs-volume-resizing-in-aws-03d77b2ceba6?source=rss-2f420b86b694------2"><img src="https://cdn-images-1.medium.com/max/1920/1*kuyk6SE0WTYDoVqMXsEQxQ.jpeg" width="1920"></a></p><p class="medium-feed-snippet">Select an instance and click Instance state. Choose Stop Instance.</p><p class="medium-feed-link"><a href="https://medium.com/@rinavillaruz/ebs-volume-resizing-in-aws-03d77b2ceba6?source=rss-2f420b86b694------2">Continue reading on Medium »</a></p></div>]]></description>
            <link>https://medium.com/@rinavillaruz/ebs-volume-resizing-in-aws-03d77b2ceba6?source=rss-2f420b86b694------2</link>
            <guid isPermaLink="false">https://medium.com/p/03d77b2ceba6</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[aws-ec2]]></category>
            <dc:creator><![CDATA[Rina Villaruz]]></dc:creator>
            <pubDate>Mon, 19 Aug 2024 21:26:17 GMT</pubDate>
            <atom:updated>2024-08-19T21:26:17.356Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[START/STOP AWS Instance Using AWS-CLI, Docker, Docker-Compose and Bash Script]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/@rinavillaruz/start-stop-aws-instance-using-aws-cli-docker-docker-compose-and-bash-script-8f9db7f65c03?source=rss-2f420b86b694------2"><img src="https://cdn-images-1.medium.com/max/1920/1*fk7qbqC2PWoafhgeVlpkqQ.jpeg" width="1920"></a></p><p class="medium-feed-snippet">Using Docker Compose, create a service for the aws-cli and make sure to add stdin_open:true, tty:true and command:help to be able to&#x2026;</p><p class="medium-feed-link"><a href="https://medium.com/@rinavillaruz/start-stop-aws-instance-using-aws-cli-docker-docker-compose-and-bash-script-8f9db7f65c03?source=rss-2f420b86b694------2">Continue reading on Medium »</a></p></div>]]></description>
            <link>https://medium.com/@rinavillaruz/start-stop-aws-instance-using-aws-cli-docker-docker-compose-and-bash-script-8f9db7f65c03?source=rss-2f420b86b694------2</link>
            <guid isPermaLink="false">https://medium.com/p/8f9db7f65c03</guid>
            <dc:creator><![CDATA[Rina Villaruz]]></dc:creator>
            <pubDate>Mon, 19 Aug 2024 21:03:12 GMT</pubDate>
            <atom:updated>2024-08-19T21:03:39.287Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Execute bash script on a remote host using Jenkins Pipeline and Publish Over SSH plugin]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/@rinavillaruz/execute-bash-script-on-a-remote-host-using-jenkins-pipeline-and-publish-over-ssh-plugin-138d2c388704?source=rss-2f420b86b694------2"><img src="https://cdn-images-1.medium.com/max/1920/1*R7ztPeesCiVkNWTh--nHjg.jpeg" width="1920"></a></p><p class="medium-feed-snippet">I decided to install Jenkins on another server because I don&#x2019;t want it to mess up with my containers and I will execute different remote&#x2026;</p><p class="medium-feed-link"><a href="https://medium.com/@rinavillaruz/execute-bash-script-on-a-remote-host-using-jenkins-pipeline-and-publish-over-ssh-plugin-138d2c388704?source=rss-2f420b86b694------2">Continue reading on Medium »</a></p></div>]]></description>
            <link>https://medium.com/@rinavillaruz/execute-bash-script-on-a-remote-host-using-jenkins-pipeline-and-publish-over-ssh-plugin-138d2c388704?source=rss-2f420b86b694------2</link>
            <guid isPermaLink="false">https://medium.com/p/138d2c388704</guid>
            <category><![CDATA[jenkins-pipeline]]></category>
            <category><![CDATA[jenkins]]></category>
            <category><![CDATA[linux]]></category>
            <dc:creator><![CDATA[Rina Villaruz]]></dc:creator>
            <pubDate>Mon, 05 Feb 2024 13:34:59 GMT</pubDate>
            <atom:updated>2024-08-19T20:58:09.880Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Launch WordPress Using Docker, Docker-Compose, and ENV file]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/@rinavillaruz/launch-wordpress-using-docker-docker-compose-and-env-file-a41764b0a49a?source=rss-2f420b86b694------2"><img src="https://cdn-images-1.medium.com/max/1920/1*AKEXKNOhtoCM3Q8NbIwmZw.jpeg" width="1920"></a></p><p class="medium-feed-snippet">I love using Docker-Compose because why not? Running Docker commands is painful.</p><p class="medium-feed-link"><a href="https://medium.com/@rinavillaruz/launch-wordpress-using-docker-docker-compose-and-env-file-a41764b0a49a?source=rss-2f420b86b694------2">Continue reading on Medium »</a></p></div>]]></description>
            <link>https://medium.com/@rinavillaruz/launch-wordpress-using-docker-docker-compose-and-env-file-a41764b0a49a?source=rss-2f420b86b694------2</link>
            <guid isPermaLink="false">https://medium.com/p/a41764b0a49a</guid>
            <category><![CDATA[docker-compose]]></category>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[wordpress]]></category>
            <category><![CDATA[linux]]></category>
            <dc:creator><![CDATA[Rina Villaruz]]></dc:creator>
            <pubDate>Mon, 05 Feb 2024 09:42:02 GMT</pubDate>
            <atom:updated>2025-01-12T10:52:27.451Z</atom:updated>
        </item>
    </channel>
</rss>