<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Richard Atodo</title>
    <description>The latest articles on DEV Community by Richard Atodo (@richard_atodo).</description>
    <link>https://dev.to/richard_atodo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2335610%2F731836a6-4162-4626-b41e-99cc5d20cfc7.jpg</url>
      <title>DEV Community: Richard Atodo</title>
      <link>https://dev.to/richard_atodo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/richard_atodo"/>
    <language>en</language>
    <item>
      <title>Leveling Up: Provisioning EKS with Terraform for My DevOps Project</title>
      <dc:creator>Richard Atodo</dc:creator>
      <pubDate>Thu, 08 May 2025 08:33:36 +0000</pubDate>
      <link>https://dev.to/richard_atodo/leveling-up-provisioning-eks-with-terraform-for-my-devops-project-dpb</link>
      <guid>https://dev.to/richard_atodo/leveling-up-provisioning-eks-with-terraform-for-my-devops-project-dpb</guid>
      <description>&lt;p&gt;Following the successful containerization and CI/CD setup for my TV Shows application (as detailed in my previous post, “&lt;a href="https://dev.to/richard_atodo/from-cosmosdb-to-dynamodb-migrating-and-containerizing-a-fastapi-react-app-28dk"&gt;From CosmosDB to DynamoDB: Migrating and Containerizing a FastAPI + React App&lt;/a&gt;”), the next logical step in my “Learn to Cloud” adventure was to embrace a more robust and scalable orchestration platform: Amazon Elastic Kubernetes Service (EKS). To manage this new layer of infrastructure as code (IaC), I turned to Terraform. This post chronicles the experience, the hurdles encountered while setting up EKS with Terraform, and the solutions that paved the way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why EKS? The Push for Orchestration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;While running Docker containers on a single EC2 instance with Docker Compose worked for the initial phase, the goal was always to explore true cloud-native practices. Kubernetes (and EKS as its managed AWS offering) promised:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scalability: Easily scale my application pods up or down based on demand.&lt;/li&gt;
&lt;li&gt;Resilience: Automatic recovery of pods and better fault tolerance.&lt;/li&gt;
&lt;li&gt;Service Discovery &amp;amp; Load Balancing: Integrated mechanisms for inter-service communication and exposing applications.&lt;/li&gt;
&lt;li&gt;Declarative Configuration: Defining my desired application state with Kubernetes manifests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Terraform: The Tool for the Job&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Manually clicking through the AWS console to set up an EKS cluster, VPC, node groups, and all associated IAM roles and security groups is complex and error-prone. With its declarative IaC approach, Terraform was the clear choice to automate this provisioning, ensuring repeatability and version control for my infrastructure. I decided to leverage the official &lt;code&gt;terraform-aws-modules/eks/aws&lt;/code&gt; module, which significantly simplifies EKS setup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Challenge 1: Mastering Terraform Remote Backend State&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before starting on EKS, best practice dictates setting up a remote backend for Terraform state. This is crucial for collaboration and preventing state loss. I opted for S3 with DynamoDB for locking.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Stumble: I ran into a really weird issue. My initial mistake was including the resource definitions for the S3 bucket and DynamoDB table within the same Terraform configuration that was also defining the EKS cluster and using that S3 bucket as its backend.&lt;/li&gt;
&lt;li&gt;The Symptom: When I ran &lt;code&gt;terraform apply&lt;/code&gt; for my EKS cluster, it seemed to be destroying the very S3 bucket and DynamoDB table that held my Terraform state! Terraform would start creating EKS resources, then attempt to "manage" (and destroy) the S3 bucket it was actively using. This caused the apply to fail spectacularly mid-operation, as Terraform couldn't save its state to a bucket it had just deleted, or release a lock from a DynamoDB table that no longer existed. The errors were clear: &lt;code&gt;NoSuchBucket&lt;/code&gt;and &lt;code&gt;ResourceNotFoundException&lt;/code&gt; for the lock table. As you can imagine, this was causing a mess, losing my Terraform state, and leading to issues with Terraform locks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Solution (Initial Part): The key was strict separation.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a completely separate Terraform configuration (e.g., in a &lt;strong&gt;remote-backend&lt;/strong&gt; subdirectory) whose sole purpose is to define and create the S3 bucket (with versioning and encryption) and the DynamoDB table for state locking. This configuration uses the default local backend.&lt;/li&gt;
&lt;li&gt;Run terraform apply on this backend-only configuration, once to provision these resources.&lt;/li&gt;
&lt;li&gt;Then, in my main EKS Terraform configuration, I only included the &lt;code&gt;backend "s3" {}&lt;/code&gt; block in &lt;code&gt;providers.tf&lt;/code&gt;, pointing to the pre-existing bucket and table. The EKS configuration itself contained no &lt;code&gt;resource&lt;/code&gt; blocks for the backend components.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;A Lingering Ghost (The Full Solution):&lt;/strong&gt; Even after separating the code, when I ran &lt;code&gt;terraform plan&lt;/code&gt; from the &lt;code&gt;eks-cluster&lt;/code&gt; directory, I noticed it still had plans to destroy 4 resources, which I knew were my state resources! The issue was that a previous (failed) apply had recorded those backend resources in the EKS configuration's state file. When I ran &lt;code&gt;terraform state list&lt;/code&gt;, the 4 resources on display were indeed the resources for the state. Terraform compared my current code (no backend resources defined) with the state file (backend resources were recorded) and concluded that since the code no longer defined them, they should be destroyed according to the state file it was managing. The fix was explicitly removing these resources from the EKS cluster's state file by running &lt;code&gt;terraform state rm &amp;lt;resource_name&amp;gt;&lt;/code&gt; for each of the four backend resources. After doing this, &lt;code&gt;terraform plan&lt;/code&gt; correctly showed 0 to destroy, acknowledging they are managed elsewhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Challenge 2: VPC Configuration — To Create or To Use?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The EKS module can create a new VPC or use an existing one.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Initial Approach:&lt;/strong&gt; I let the module create a new VPC. This approach is often cleaner and ensures that all EKS tagging requirements are met automatically. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The “VpcLimitExceeded” Wall:&lt;/strong&gt; During one &lt;code&gt;terraform apply&lt;/code&gt;, I encountered the &lt;code&gt;VpcLimitExceeded&lt;/code&gt; error. My AWS account had reached its default limit of 5 VPCs per region. This was because Terraform created some resources, including VPCs, during my failed &lt;code&gt;terraform apply&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Solutions &amp;amp; Considerations:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Delete Unused VPCs:&lt;/strong&gt; The quickest fix was to log into the AWS console and delete old, unused VPCs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Challenge 3: “Resource Already Exists” — Cleaning Up After Failed Runs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;EKS cluster creation is a lengthy process. If an &lt;strong&gt;apply&lt;/strong&gt; fails midway, some resources might be created while others aren't, and Terraform might not have recorded their creation in the state file if it crashed before saving, just like what happened with the VPCs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Problem:&lt;/strong&gt; On subsequent &lt;code&gt;terraform apply&lt;/code&gt; attempts, I encountered errors like &lt;code&gt;AlreadyExistsException&lt;/code&gt; for a KMS Alias (&lt;code&gt;alias/eks/ltc-eks-cluster&lt;/code&gt;) and a CloudWatch Log Group (&lt;code&gt;/aws/eks/ltc-eks-cluster/cluster&lt;/code&gt;). Terraform was trying to create these, but they already existed from a previous partial run.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Manual Deletion (Use with Caution):&lt;/strong&gt; For the KMS alias, since I was certain it was a remnant and not in use, I manually deleted it via the AWS console before re-running &lt;code&gt;apply&lt;/code&gt; was the option I took. This is riskier if you're unsure of the resource's origin.&lt;/p&gt;

&lt;p&gt;Ideally, Terraform modules are idempotent, but sometimes with complex, multi-step creations like EKS, partial failures can leave things in a state that requires manual intervention.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Sweet Success: A Running EKS Cluster&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After navigating these hurdles, &lt;code&gt;terraform apply&lt;/code&gt; finally completed, and the &lt;code&gt;outputs.tf&lt;/code&gt; provided the command to configure &lt;code&gt;kubectl&lt;/code&gt;. Seeing &lt;code&gt;kubectl get nodes&lt;/code&gt; return the worker nodes from my new EKS cluster was a very satisfying moment!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Learnings from EKS &amp;amp; Terraform:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backend State is Sacred:&lt;/strong&gt; Treat its setup carefully and keep it separate. Understand how state inconsistencies can lead to unexpected plans.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Understand Module Defaults &amp;amp; Requirements:&lt;/strong&gt; The EKS module is powerful but has expectations (like VPC subnet tagging for load balancers).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Service Limits are Real:&lt;/strong&gt; Be aware of them, especially for resources like VPCs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failed Applies Need Cleanup/Import:&lt;/strong&gt; Don’t just keep re-running apply. Investigate "already exists" errors and either import the resource or safely delete it if it's an orphaned remnant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Patience:&lt;/strong&gt; EKS cluster provisioning takes time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find the repository &lt;a href="https://github.com/richardatodo/ltc-devops-project" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next Steps: Deploying the Application&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With the EKS cluster provisioned by Terraform and my CI/CD pipeline pushing images to ECR, the next phase is to write the Kubernetes YAML manifests (Deployments, Services, ConfigMaps, Secrets) to deploy my React frontend and FastAPI backend onto the cluster. This will involve configuring Ingress for external access and IRSA for secure AWS service access from my backend pods. The journey to a fully orchestrated application continues!&lt;/p&gt;

</description>
      <category>cicd</category>
      <category>terraform</category>
      <category>kubernetes</category>
      <category>eks</category>
    </item>
    <item>
      <title>From CosmosDB to DynamoDB: Migrating and Containerizing a FastAPI + React App</title>
      <dc:creator>Richard Atodo</dc:creator>
      <pubDate>Mon, 21 Apr 2025 13:19:30 +0000</pubDate>
      <link>https://dev.to/richard_atodo/from-cosmosdb-to-dynamodb-migrating-and-containerizing-a-fastapi-react-app-28dk</link>
      <guid>https://dev.to/richard_atodo/from-cosmosdb-to-dynamodb-migrating-and-containerizing-a-fastapi-react-app-28dk</guid>
      <description>&lt;p&gt;Building and deploying a full-stack application involves more than just writing code. It's a journey through infrastructure, automation, and inevitable troubleshooting. As part of the &lt;strong&gt;Learn to Cloud initiative&lt;/strong&gt;, I recently expanded on a previous &lt;strong&gt;Phase 2 Capstone Project (a Serverless Movies API)&lt;/strong&gt; to focus on applying core &lt;strong&gt;DevOps practices&lt;/strong&gt; like containerization, CI/CD, and eventually observability/monitoring. The target was a simple TV show application (a sample is available &lt;a href="https://github.com/rishabkumar7/ltc-devops-project" rel="noopener noreferrer"&gt;here&lt;/a&gt;. The path from local development to a containerized deployment on AWS using CI/CD was filled with valuable lessons. Here's a look at the experience, the hurdles, and how I overcame them.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Goal: A DevOps-Focused TV Show App
&lt;/h3&gt;

&lt;p&gt;The application idea was straightforward: a web app where users could browse TV shows and view details about their seasons and episodes. The core goal, however, was applying modern DevOps techniques using this stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backend:&lt;/strong&gt; FastAPI (Python) for its speed and ease of use.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; React for building the user interface.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database:&lt;/strong&gt; Initially Azure Cosmos DB, but migrated to AWS DynamoDB.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment:&lt;/strong&gt; Docker containers running on an AWS EC2 instance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automation:&lt;/strong&gt; GitHub Actions for CI/CD.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Challenge 1: Migrating the Backend (Cosmos DB to DynamoDB)&lt;/strong&gt;&lt;br&gt;
One of the first major tasks was switching the database. Moving from Azure Cosmos DB’s SQL API to DynamoDB required more than just changing connection strings.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Different SDKs (&lt;code&gt;azure-cosmos vs. boto3&lt;/code&gt;) and fundamentally different query models (SQL-like queries vs. DynamoDB's key-value/document operations like &lt;code&gt;scan&lt;/code&gt; and &lt;code&gt;get_item&lt;/code&gt;). AWS authentication also needed handling.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; I refactored the backend’s data access layer to use boto3. Listing all shows involved a scan operation (noting its potential performance impact on large tables), while fetching specific show details used the efficient get_item. For authentication, I transitioned from potential key-based access to using an IAM Role attached to the EC2 instance, which is much more secure. This involved ensuring the role had the correct DynamoDB permissions (&lt;code&gt;dynamodb:Scan&lt;/code&gt;, &lt;code&gt;dynamodb:GetItem&lt;/code&gt;, etc.).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Challenge 2: Containerizing the Stack&lt;/strong&gt;&lt;br&gt;
Dockerizing the FastAPI backend and the React frontend (using an Nginx server) seemed standard, but ensuring they worked together smoothly within Docker Compose required careful configuration. The multi-stage build for the React/Nginx frontend was key to keeping the final image small.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Challenge 3: Setting Up the EC2 Environment&lt;/strong&gt;&lt;br&gt;
I chose Amazon Linux 2023 on a &lt;code&gt;t4g.medium&lt;/code&gt; instance (for that Graviton price-performance!). Getting the environment ready for Docker threw up some unexpected curveballs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Installing Docker Compose. My initial attempts (&lt;code&gt;sudo dnf install docker-compose&lt;/code&gt;) failed because the package name corresponds to the deprecated V1. Trying the correct V2 package (&lt;code&gt;sudo dnf install docker-compose-plugin&lt;/code&gt;) also failed initially!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; After some head-scratching, I realized the dnf package manager's cache was likely stale. Running &lt;code&gt;sudo dnf clean all&lt;/code&gt; before retrying the &lt;code&gt;docker-compose-plugin&lt;/code&gt; installation resolved the issue. This was a good reminder that package managers aren't infallible and sometimes need a refresh.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Challenge 4: “Unable to Connect” — The Dreaded Networking Hurdle&lt;/strong&gt;&lt;br&gt;
With the containers seemingly running via &lt;code&gt;docker compose up&lt;/code&gt;, I tried accessing the frontend from my browser using the EC2 instance's public IP and the mapped port (3000). Result: "Unable to connect."&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; The connection wasn’t even reaching the application. This almost always points to a firewall issue.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; The culprit was the EC2 Security Group. It acts as a stateful firewall, and I hadn’t created an inbound rule to allow traffic on TCP port 3000 from my IP address (or &lt;code&gt;0.0.0.0/0&lt;/code&gt; for testing). Adding this rule immediately fixed the connection issue. Testing connectivity locally on the instance (&lt;code&gt;curl localhost:3000&lt;/code&gt;) also helped confirm the containers were running and mapped correctly, isolating the problem to the external firewall (the Security Group).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Challenge 5: Docker Compose Runtime Issues&lt;/strong&gt;&lt;br&gt;
Even after connecting, the application wasn’t fully working. &lt;code&gt;docker compose ps&lt;/code&gt; showed the frontend running, but the backend was missing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Docker Compose logs (&lt;code&gt;docker compose logs api-backend&lt;/code&gt;) revealed the backend container was failing to start because required environment variables (&lt;code&gt;AWS_REGION&lt;/code&gt;, &lt;code&gt;DYNAMODB_TABLE_NAME&lt;/code&gt;) were missing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; I had created a &lt;code&gt;.env&lt;/code&gt; file, but I had to double-check:&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Was it in the same directory as &lt;code&gt;docker-compose.yml&lt;/code&gt;? (Checked with &lt;code&gt;ls -a&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Did it contain the correct variable names and values? (Checked with &lt;code&gt;cat .env&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Was the &lt;code&gt;docker-compose.yml&lt;/code&gt; correctly referencing these variables using the &lt;code&gt;${VAR}&lt;/code&gt; syntax in the &lt;code&gt;environment&lt;/code&gt; section for the backend service? Correcting a small mistake here and restarting with &lt;code&gt;docker compose down &amp;amp;&amp;amp; docker compose up -d&lt;/code&gt; brought the backend online.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Challenge 6: Automating Builds with GitHub Actions &amp;amp; AWS OIDC&lt;/strong&gt;&lt;br&gt;
Manually building and deploying gets tedious fast. Setting up a CI/CD pipeline using GitHub Actions to build images and push them to AWS ECR was the next goal. This involved securely authenticating GitHub Actions with AWS using OpenID Connect (OIDC).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; The workflow failed during the AWS authentication step with &lt;code&gt;Error: Could not assume role with OIDC: No OpenIDConnect provider found...&lt;/code&gt;. Even after creating the OIDC provider in AWS IAM, further errors occurred when debugging the IAM Role's Trust Policy.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; This required careful configuration in AWS IAM:&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Creating the OIDC Provider:&lt;/strong&gt; Explicitly adding &lt;code&gt;token.actions.githubusercontent.com&lt;/code&gt; as an identity provider in IAM.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Configuring the Role Trust Policy:&lt;/strong&gt; This was tricky. The initial policy was wrong (it trusted EC2, not GitHub OIDC). The correct policy needed:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Principal&lt;/code&gt;: Set to &lt;code&gt;"Federated"&lt;/code&gt; referencing the OIDC provider's ARN.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Action&lt;/code&gt;: Set to &lt;code&gt;"sts:AssumeRoleWithWebIdentity"&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Condition&lt;/code&gt;: Carefully crafting the &lt;code&gt;sub&lt;/code&gt; claim condition to match the &lt;code&gt;repo:ORG/REPO:ref...&lt;/code&gt; format, ensuring I didn't mistakenly include the &lt;code&gt;https://github.com/&lt;/code&gt; prefix. Getting the trust policy exactly right allowed the workflow to assume the role and push images to ECR successfully.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Key Takeaways
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cloud Services Have Nuances:&lt;/strong&gt; Migrating between databases or setting up authentication requires understanding the specific service’s model (e.g., DynamoDB queries, AWS IAM roles).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Firewalls are Foundational:&lt;/strong&gt; Security Groups are often the first place to look for connectivity issues to EC2 instances.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Container Orchestration Needs Precision:&lt;/strong&gt; Docker Compose relies heavily on correct file paths (&lt;code&gt;.env&lt;/code&gt;, &lt;code&gt;docker-compose.yml&lt;/code&gt;), environment variable syntax, and networking between containers. Logs are essential (&lt;code&gt;docker compose logs &amp;lt;service&amp;gt;&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Package Managers Can Be Quirky:&lt;/strong&gt; Sometimes, a simple cache clean (&lt;code&gt;dnf clean all&lt;/code&gt;) is all you need. Know the difference between V1 and V2 (e.g., &lt;code&gt;docker-compose&lt;/code&gt; vs &lt;code&gt;docker compose&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Secure CI/CD Requires Careful Setup:&lt;/strong&gt; OIDC is powerful for keyless authentication but demands precise configuration of the IAM provider and role trust policies in AWS.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Iterative Debugging is Key:&lt;/strong&gt; Very rarely does everything work on the first try. Systematically checking logs, configurations, and documentation is crucial.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Conclusion &amp;amp; Next Steps
&lt;/h3&gt;

&lt;p&gt;This project provided a rich learning experience across backend development, cloud database migration, containerization, EC2 instance management, and CI/CD automation within the “Learn to Cloud” framework. Encountering and solving issues with package management, security groups, environment variables, and IAM policies was invaluable. The application now runs on EC2, with deployments automated via GitHub Actions pushing to ECR. See repo &lt;a href="https://github.com/richardatodo/ltc-devops-project" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This provides a solid foundation. The next exciting phase involves using &lt;strong&gt;Infrastructure as Code (IaC) with Terraform&lt;/strong&gt; to provision the AWS resources automatically. This could set the stage for deploying the application onto &lt;strong&gt;Amazon Elastic Kubernetes Service (EKS)&lt;/strong&gt; for enhanced scalability, resilience, and management, further deepening the exploration of DevOps practices on AWS.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>aws</category>
      <category>docker</category>
      <category>dynamodb</category>
    </item>
    <item>
      <title>Setting Up an Automated Java Build and Deployment Pipeline with AWS CodeArtifact</title>
      <dc:creator>Richard Atodo</dc:creator>
      <pubDate>Fri, 14 Mar 2025 19:44:51 +0000</pubDate>
      <link>https://dev.to/richard_atodo/setting-up-an-automated-java-build-and-deployment-pipeline-with-aws-codeartifact-2kko</link>
      <guid>https://dev.to/richard_atodo/setting-up-an-automated-java-build-and-deployment-pipeline-with-aws-codeartifact-2kko</guid>
      <description>&lt;h2&gt;
  
  
  &lt;strong&gt;Introduction&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;In this guide, I will walk you through how I set up an EC2 instance to compile, package, and publish a Java-based Maven project to AWS CodeArtifact. This setup ensures a robust and reusable package management process in a cloud-native CI/CD pipeline.  &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Step 1: Setting Up the EC2 Instance&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1.1 Launching an EC2 Instance&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;I started by launching an &lt;strong&gt;Amazon Linux 2023 t3.micro&lt;/strong&gt; EC2 instance with the following specifications:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AMI:&lt;/strong&gt; Amazon Linux 2023
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instance Type:&lt;/strong&gt; t3.micro
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage:&lt;/strong&gt; 8 GB (default)
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security Group:&lt;/strong&gt; Allowed SSH (port 22) and HTTP (port 80)
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1.2 Connecting to the EC2 Instance&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;After launching the instance, I connected via SSH using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-i&lt;/span&gt; my-key.pem ec2-user@&amp;lt;EC2-Public-IP&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This provided direct access to the instance for software installation.  &lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Step 2: Installing Java and Maven&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Maven is required to build and manage Java projects, while Java is needed to run Maven-based applications.  &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2.1 Installing Java Amazon Corretto 8&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Amazon Corretto 8 is a free, production-ready distribution of OpenJDK. I installed it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; java-1.8.0-amazon-corretto-devel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, I set environment variables to ensure Java was properly recognized:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;JAVA_HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/lib/jvm/java-1.8.0-amazon-corretto.x86_64
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$JAVA_HOME&lt;/span&gt;/bin:&lt;span class="nv"&gt;$PATH&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make this change permanent, I added the paths to &lt;code&gt;~/.bashrc&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'export JAVA_HOME=/usr/lib/jvm/java-1.8.0-amazon-corretto.x86_64'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.bashrc
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'export PATH=$JAVA_HOME/bin:$PATH'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.bashrc
&lt;span class="nb"&gt;source&lt;/span&gt; ~/.bashrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I verified the installation with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;java &lt;span class="nt"&gt;-version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;2.2 Installing Maven 3.5.2&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Maven 3.5.2 was required to build the Java web app. I downloaded and extracted it manually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wget https://archive.apache.org/dist/maven/maven-3/3.5.2/binaries/apache-maven-3.5.2-bin.tar.gz
&lt;span class="nb"&gt;sudo tar&lt;/span&gt; &lt;span class="nt"&gt;-xzf&lt;/span&gt; apache-maven-3.5.2-bin.tar.gz &lt;span class="nt"&gt;-C&lt;/span&gt; /opt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, I added it to my system PATH:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"export PATH=/opt/apache-maven-3.5.2/bin:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.bashrc
&lt;span class="nb"&gt;source&lt;/span&gt; ~/.bashrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I confirmed Maven was installed by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mvn &lt;span class="nt"&gt;-version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  &lt;strong&gt;Step 3: Cloning and Configuring the Java Project&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I initialized a Git repository on my EC2 instance and connected it to my GitHub repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git init
git remote add origin https://github.com/richardatodo/nextwork-web-project.git
git pull origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside the project directory, I ensured the required dependencies were defined in &lt;code&gt;pom.xml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependencies&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;junit&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;junit&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;3.8.1&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;scope&amp;gt;&lt;/span&gt;test&lt;span class="nt"&gt;&amp;lt;/scope&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependencies&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, I built the project to verify everything was working:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mvn compile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  &lt;strong&gt;Step 4: Setting Up AWS CodeArtifact&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;4.1 Creating a CodeArtifact Repository and Domain&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Got it! I'll update the blog post to reflect that you created the &lt;strong&gt;CodeArtifact domain and repository via the AWS Console&lt;/strong&gt; instead of using the AWS CLI.  &lt;/p&gt;

&lt;p&gt;Here’s the revised section:  &lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Step 4: Setting Up AWS CodeArtifact&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I created the &lt;strong&gt;CodeArtifact domain&lt;/strong&gt; and &lt;strong&gt;repository&lt;/strong&gt; via the AWS Management Console:  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Navigate to AWS CodeArtifact:&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open the &lt;strong&gt;AWS Console&lt;/strong&gt; and go to &lt;strong&gt;CodeArtifact&lt;/strong&gt;.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Create a CodeArtifact Domain:&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click &lt;strong&gt;Create domain&lt;/strong&gt;.
&lt;/li&gt;
&lt;li&gt;Enter the &lt;strong&gt;domain name&lt;/strong&gt;: &lt;code&gt;nextwork&lt;/code&gt;.
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create domain&lt;/strong&gt;.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Create a CodeArtifact Repository:&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click &lt;strong&gt;Create repository&lt;/strong&gt;.
&lt;/li&gt;
&lt;li&gt;Enter the &lt;strong&gt;repository name&lt;/strong&gt;: &lt;code&gt;nextwork-devops-cicd&lt;/code&gt;.
&lt;/li&gt;
&lt;li&gt;Select the domain &lt;code&gt;nextwork&lt;/code&gt;.
&lt;/li&gt;
&lt;li&gt;(Optional) Enable &lt;strong&gt;Upstream repositories&lt;/strong&gt; if needed.
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create repository&lt;/strong&gt;.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;4.2 Configuring IAM Permissions&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;To allow EC2 to interact with CodeArtifact, I created an IAM policy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"codeartifact:GetAuthorizationToken"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"codeartifact:GetRepositoryEndpoint"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"codeartifact:ReadFromRepository"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"codeartifact:PublishPackageVersion"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"codeartifact:PutPackageMetadata"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sts:GetServiceBearerToken"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"StringEquals"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"sts:AWSServiceName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"codeartifact.amazonaws.com"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I attached this policy to an IAM role and associated it with my EC2 instance.  &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;4.3 Generating an Authorization Token&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;To authenticate Maven with CodeArtifact, I generated a token and stored it in an environment variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CODEARTIFACT_AUTH_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws codeartifact get-authorization-token &lt;span class="nt"&gt;--domain&lt;/span&gt; nextwork &lt;span class="nt"&gt;--query&lt;/span&gt; authorizationToken &lt;span class="nt"&gt;--output&lt;/span&gt; text&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  &lt;strong&gt;Step 5: Configuring Maven to Use CodeArtifact&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I created a &lt;code&gt;settings.xml&lt;/code&gt; file in my project directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;settings&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;servers&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;server&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;id&amp;gt;&lt;/span&gt;nextwork-nextwork-devops-cicd&lt;span class="nt"&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;username&amp;gt;&lt;/span&gt;aws&lt;span class="nt"&gt;&amp;lt;/username&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;password&amp;gt;&lt;/span&gt;${env.CODEARTIFACT_AUTH_TOKEN}&lt;span class="nt"&gt;&amp;lt;/password&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/server&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/servers&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;profiles&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;profile&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;id&amp;gt;&lt;/span&gt;nextwork-nextwork-devops-cicd&lt;span class="nt"&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;activation&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;activeByDefault&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/activeByDefault&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/activation&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;repositories&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;repository&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;id&amp;gt;&lt;/span&gt;nextwork-nextwork-devops-cicd&lt;span class="nt"&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;url&amp;gt;&lt;/span&gt;https://nextwork-617439230997.d.codeartifact.us-east-1.amazonaws.com/maven/nextwork-devops-cicd/&lt;span class="nt"&gt;&amp;lt;/url&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/repository&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/repositories&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/profile&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/profiles&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/settings&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9f1y97wfwg7lopuof1r7.png" alt="Image description" width="800" height="449"&gt;
&lt;/h2&gt;

&lt;p&gt;Then, Run the Maven compile command, which uses the settings.xml file we just configured::&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mvn &lt;span class="nt"&gt;-s&lt;/span&gt; settings.xml compile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fulahx9kqkroxapeofm9s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fulahx9kqkroxapeofm9s.png" alt="Image description" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Step 6: Publishing the Package to CodeArtifact&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I updated my &lt;code&gt;pom.xml&lt;/code&gt; to include &lt;code&gt;distributionManagement&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;distributionManagement&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;repository&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;id&amp;gt;&lt;/span&gt;nextwork-nextwork-devops-cicd&lt;span class="nt"&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;url&amp;gt;&lt;/span&gt;https://nextwork-617439230997.d.codeartifact.us-east-1.amazonaws.com/maven/nextwork-devops-cicd/&lt;span class="nt"&gt;&amp;lt;/url&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/repository&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/distributionManagement&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9xhxi9n03r90lre5k4jv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9xhxi9n03r90lre5k4jv.png" alt="Image description" width="800" height="449"&gt;&lt;/a&gt;&lt;br&gt;
Then, I deployed the package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mvn &lt;span class="nt"&gt;-s&lt;/span&gt; settings.xml deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This guide covered how I set up an AWS CodeArtifact repository, configured an EC2 instance to authenticate with it, and successfully deployed a Maven package. This process forms the foundation for integrating package management into a CI/CD pipeline, ensuring secure and scalable software delivery.  &lt;/p&gt;




</description>
      <category>devops</category>
      <category>aws</category>
      <category>cicd</category>
      <category>java</category>
    </item>
    <item>
      <title>HNG Stage 2: Deploying a FastAPI Application with a CI/CD Pipeline</title>
      <dc:creator>Richard Atodo</dc:creator>
      <pubDate>Sun, 16 Feb 2025 09:23:22 +0000</pubDate>
      <link>https://dev.to/richard_atodo/hng-stage-2-deploying-a-fastapi-application-with-a-cicd-pipeline-3b5l</link>
      <guid>https://dev.to/richard_atodo/hng-stage-2-deploying-a-fastapi-application-with-a-cicd-pipeline-3b5l</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;As part of the HNG DevOps internship, Stage 2 required deploying a FastAPI application using Docker and setting up a CI/CD pipeline via GitHub Actions. This was an exciting challenge that tested my skills in cloud infrastructure, automation, and debugging under real-world conditions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Requirements Overview
&lt;/h2&gt;

&lt;p&gt;The project had several key requirements:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Implement a missing endpoint for book retrieval&lt;/li&gt;
&lt;li&gt;Set up CI/CD pipelines using GitHub Actions&lt;/li&gt;
&lt;li&gt;Containerize the application using Docker&lt;/li&gt;
&lt;li&gt;Deploy and serve the application through Nginx&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Building the Book API
&lt;/h3&gt;

&lt;p&gt;The first challenge was implementing the missing endpoint to retrieve a book by ID. The solution required careful consideration of error handling and response formatting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@router.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/{book_id}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response_model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_book&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;book_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;books&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_books&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;book_id&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;books&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;JSONResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTP_404_NOT_FOUND&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;detail&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Book not found&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;books&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;book_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setting Up GitHub Actions for CI/CD
&lt;/h2&gt;

&lt;p&gt;The CI/CD setup was fascinating. I used GitHub Actions to create two essential workflows:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;test.yml&lt;/code&gt; (CI Pipeline)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Test FastAPI Application&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check out repository&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Python&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.12'&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;python -m pip install --upgrade pip&lt;/span&gt;
          &lt;span class="s"&gt;pip install -r requirements.txt&lt;/span&gt;
          &lt;span class="s"&gt;pip install pytest pytest-cov&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run pytest&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;pytest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;deploy.yml&lt;/code&gt; (CD Pipeline)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy FastAPI Application&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Python&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.12'&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;python -m pip install --upgrade pip&lt;/span&gt;
          &lt;span class="s"&gt;pip install -r requirements.txt&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run tests&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pytest&lt;/span&gt;

  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Configure SSH&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;SSH_PRIVATE_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SSH_PRIVATE_KEY }}&lt;/span&gt;
          &lt;span class="na"&gt;SSH_HOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SSH_HOST }}&lt;/span&gt;
          &lt;span class="na"&gt;SSH_USERNAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SSH_USERNAME }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;mkdir -p ~/.ssh/&lt;/span&gt;
          &lt;span class="s"&gt;echo "$SSH_PRIVATE_KEY" &amp;gt; ~/.ssh/deploy_key&lt;/span&gt;
          &lt;span class="s"&gt;chmod 600 ~/.ssh/deploy_key&lt;/span&gt;
          &lt;span class="s"&gt;ssh-keyscan -H $SSH_HOST &amp;gt;&amp;gt; ~/.ssh/known_hosts&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to AWS EC2&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;SSH_HOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SSH_HOST }}&lt;/span&gt;
          &lt;span class="na"&gt;SSH_USERNAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SSH_USERNAME }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;ssh -i ~/.ssh/deploy_key $SSH_USERNAME@$SSH_HOST &amp;lt;&amp;lt; 'ENDSSH'&lt;/span&gt;
            &lt;span class="s"&gt;# Create project directory if it doesn't exist&lt;/span&gt;
            &lt;span class="s"&gt;mkdir -p ~/fastapi-book-project&lt;/span&gt;

            &lt;span class="s"&gt;# Navigate to project directory&lt;/span&gt;
            &lt;span class="s"&gt;cd ~/fastapi-book-project || exit 1&lt;/span&gt;

            &lt;span class="s"&gt;# Check if repository exists, if not clone it&lt;/span&gt;
            &lt;span class="s"&gt;if [ ! -d .git ]; then&lt;/span&gt;
              &lt;span class="s"&gt;git clone https://github.com/${{ github.repository }}.git .&lt;/span&gt;
            &lt;span class="s"&gt;fi&lt;/span&gt;

            &lt;span class="s"&gt;# Fetch and reset to main&lt;/span&gt;
            &lt;span class="s"&gt;git fetch --all&lt;/span&gt;
            &lt;span class="s"&gt;git reset --hard origin/main&lt;/span&gt;

            &lt;span class="s"&gt;# Check if docker-compose exists&lt;/span&gt;
            &lt;span class="s"&gt;if [ ! -f docker-compose.yml ]; then&lt;/span&gt;
              &lt;span class="s"&gt;echo "docker-compose.yml not found!"&lt;/span&gt;
              &lt;span class="s"&gt;exit 1&lt;/span&gt;
            &lt;span class="s"&gt;fi&lt;/span&gt;

            &lt;span class="s"&gt;# Stop running containers&lt;/span&gt;
            &lt;span class="s"&gt;docker-compose down || true&lt;/span&gt;

            &lt;span class="s"&gt;# Build and start containers&lt;/span&gt;
            &lt;span class="s"&gt;docker-compose up -d --build&lt;/span&gt;

            &lt;span class="s"&gt;# Verify deployment&lt;/span&gt;
            &lt;span class="s"&gt;docker ps&lt;/span&gt;

            &lt;span class="s"&gt;# Check if the application is responding&lt;/span&gt;
            &lt;span class="s"&gt;sleep 10&lt;/span&gt;
            &lt;span class="s"&gt;curl -f http://localhost:8000/api/v1/books || echo "Warning: Application not responding"&lt;/span&gt;
          &lt;span class="s"&gt;ENDSSH&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Dockerizing the FastAPI Application
&lt;/h2&gt;

&lt;p&gt;A key part of this challenge was containerizing the FastAPI application to ensure consistent deployment. The Dockerfile used was as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.12-slim&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;docker-compose.yml&lt;/code&gt;:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.8'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;fastapi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fastapi_app&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8000:8000"&lt;/span&gt;

  &lt;span class="na"&gt;nginx&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:latest&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx_reverse_proxy&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./nginx.conf:/etc/nginx/conf.d/default.conf&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;80:80"&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;fastapi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configuring Nginx as a Reverse Proxy
&lt;/h2&gt;

&lt;p&gt;To make the application accessible on port 80, I configured Nginx as a reverse proxy:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Installed Nginx:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;sudo &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;nginx &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Created a new configuration file at &lt;code&gt;/etc/nginx/sites-available/fastapi&lt;/code&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;   &lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

       &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
           &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://localhost:8000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
           &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
           &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Real-IP&lt;/span&gt; &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Enabled the configuration and restarted Nginx:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;sudo ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /etc/nginx/sites-available/fastapi /etc/nginx/sites-enabled/
   &lt;span class="nb"&gt;sudo rm&lt;/span&gt; /etc/nginx/sites-enabled/default
   &lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Debugging and Fixing Issues
&lt;/h2&gt;

&lt;p&gt;During deployment, I encountered several challenges, including:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Port Conflict Errors&lt;/strong&gt;: Docker was already binding to port 8000. Solved by stopping previous instances with &lt;code&gt;docker stop $(docker ps -q)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Systemd Service Failure&lt;/strong&gt;: FastAPI failed due to an address bind issue. Fixed by ensuring no other processes were using port 8000.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Actions Deployment Failures&lt;/strong&gt;: Resolved by properly configuring SSH keys and setting up secrets in GitHub.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This experience strengthened my ability to deploy containerized applications with automation and troubleshoot real-world deployment issues. The hands-on challenge provided deep insights into CI/CD best practices, making it a valuable learning experience.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>ec2</category>
      <category>devops</category>
      <category>docker</category>
    </item>
    <item>
      <title>Deploying the Number Classification API Using AWS Lambda Function URL</title>
      <dc:creator>Richard Atodo</dc:creator>
      <pubDate>Wed, 05 Feb 2025 11:16:50 +0000</pubDate>
      <link>https://dev.to/richard_atodo/deploying-the-number-classification-api-using-aws-lambda-function-url-4l5f</link>
      <guid>https://dev.to/richard_atodo/deploying-the-number-classification-api-using-aws-lambda-function-url-4l5f</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;After completing my Number Classification API for Stage 1 of the HNG DevOps Internship, I deployed it. The goal was to make my API publicly accessible while ensuring seamless integration with AWS Lambda. Instead of using API Gateway, I deployed the API using AWS Lambda Function URLs, which provided a simpler and more direct approach to serving HTTP requests.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbmotcjw0qjr57rur122v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbmotcjw0qjr57rur122v.png" alt="Image description" width="800" height="224"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the Number Classification API?
&lt;/h2&gt;

&lt;p&gt;The Number Classification API is a FastAPI-based service that classifies numbers based on their mathematical properties. It can determine if a number is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Prime&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Armstrong&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Perfect&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Odd or even&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sum of its digits&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fun facts about the number&lt;/strong&gt; (retrieved from an external API)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This API was designed to handle both positive and negative numbers, as well as floating-point inputs, which are converted to integers. see my &lt;a href="https://github.com/richardatodo/number-classification-api" rel="noopener noreferrer"&gt;GitHub Repo&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing AWS Lambda Function URL Over API Gateway
&lt;/h2&gt;

&lt;p&gt;Initially, I considered deploying my FastAPI application using API Gateway and Lambda. However, I decided to use AWS Lambda Function URLs instead for the following reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Simplicity&lt;/strong&gt;: Lambda Function URLs provide a built-in HTTP endpoint, reducing the need for additional API Gateway configurations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Direct Invocation&lt;/strong&gt;: Function URLs allow direct access to the Lambda function without needing an API Gateway mapping.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lower Cost&lt;/strong&gt;: Since API Gateway incurs additional costs, using Lambda Function URLs helped reduce expenses.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Deployment Process
&lt;/h2&gt;

&lt;p&gt;Here’s a step-by-step breakdown of how I deployed my FastAPI application on AWS Lambda:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Preparing the Deployment Package
&lt;/h3&gt;

&lt;p&gt;To deploy a FastAPI application on AWS Lambda, it must be packaged correctly. I installed the necessary dependencies and zipped the application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Compress-Archive venv&lt;span class="se"&gt;\L&lt;/span&gt;ib&lt;span class="se"&gt;\s&lt;/span&gt;ite-packages&lt;span class="se"&gt;\*&lt;/span&gt; lambda.zip 
Compress-Archive .&lt;span class="se"&gt;\m&lt;/span&gt;ain.py &lt;span class="nt"&gt;-Update&lt;/span&gt; lambda.zip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Creating an AWS Lambda Function
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Logged into the &lt;strong&gt;AWS Lambda console&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Created a new Lambda function using &lt;strong&gt;Python 3.9+&lt;/strong&gt; as the runtime.&lt;/li&gt;
&lt;li&gt;Uploaded the &lt;code&gt;lambda.zip&lt;/code&gt; file as the function’s code.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Enabling Lambda Function URL
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Navigated to the &lt;strong&gt;Function URL&lt;/strong&gt; section in the Lambda console.&lt;/li&gt;
&lt;li&gt;Enabled the &lt;strong&gt;Function URL&lt;/strong&gt; and set the authentication type to &lt;code&gt;NONE&lt;/code&gt; to allow public access.&lt;/li&gt;
&lt;li&gt;Copied the generated Function URL for testing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Configuring CORS
&lt;/h3&gt;

&lt;p&gt;Since the API was designed to be publicly accessible, I enabled CORS to allow requests from any origin. This was achieved by adding the &lt;code&gt;CORSMiddleware&lt;/code&gt; in the FastAPI application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi.middleware.cors&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CORSMiddleware&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;CORSMiddleware&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;allow_origins&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;allow_credentials&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;allow_methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;allow_headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Testing the API
&lt;/h3&gt;

&lt;p&gt;To ensure everything was working correctly, I tested the deployed API using &lt;code&gt;curl&lt;/code&gt; and a web browser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; GET &lt;span class="s2"&gt;"https://your-lambda-function-url/api/classify-number?number=153"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response returned valid JSON data, confirming a successful deployment!&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges Faced
&lt;/h2&gt;

&lt;p&gt;Deploying FastAPI on AWS Lambda wasn’t without its challenges. Some key issues I encountered included:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Import Errors&lt;/strong&gt;: Initially, I faced &lt;code&gt;ImportModuleError: No module named 'pydantic_core._pydantic_core'&lt;/code&gt;, which was due to package incompatibilities. Switching to an AWS-supported Python version and ensuring all dependencies were installed inside the Lambda layer resolved this issue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CORS Issues&lt;/strong&gt;: The first few API requests were blocked due to missing CORS headers. Adding &lt;code&gt;allow_origins=["*"]&lt;/code&gt; in &lt;code&gt;CORSMiddleware&lt;/code&gt; fixed this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Number Validation Errors&lt;/strong&gt;: The API initially returned &lt;code&gt;400 Bad Request&lt;/code&gt; for negative numbers and floating-point values. After refining the number validation logic, all valid numbers were correctly processed.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;p&gt;Deploying a FastAPI application using AWS Lambda Function URLs proved to be an efficient and cost-effective approach. Some important lessons I learned include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AWS Lambda is powerful for lightweight APIs&lt;/strong&gt;: It eliminates the need for managing infrastructure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Function URLs simplify deployment&lt;/strong&gt;: No need for API Gateway if a simple public endpoint is required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proper error handling is crucial&lt;/strong&gt;: Ensuring valid JSON responses helps in debugging and API usability.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CORS must be configured properly&lt;/strong&gt;: Without it, web applications may fail to make requests to the API.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This project was a great learning experience and a significant milestone in my cloud journey. It reinforced my understanding of AWS Lambda, FastAPI, and CORS configurations. As I progress in the HNG DevOps Internship, I look forward to tackling more complex deployments and optimizing API performance.&lt;/p&gt;

&lt;p&gt;If you’re considering deploying a FastAPI service on AWS Lambda, I highly recommend trying Lambda Function URLs for their ease of use and cost efficiency!&lt;/p&gt;




&lt;p&gt;What are your thoughts on this approach? Let’s discuss this in the comments! 🚀&lt;/p&gt;

</description>
      <category>api</category>
      <category>python</category>
      <category>aws</category>
      <category>lambda</category>
    </item>
    <item>
      <title>Setting Up NGINX Web Server on AWS EC2: My HNG DevOps Stage 0 Experience</title>
      <dc:creator>Richard Atodo</dc:creator>
      <pubDate>Thu, 30 Jan 2025 00:17:17 +0000</pubDate>
      <link>https://dev.to/richard_atodo/setting-up-nginx-web-server-on-aws-ec2-my-hng-devops-stage-0-experience-1mme</link>
      <guid>https://dev.to/richard_atodo/setting-up-nginx-web-server-on-aws-ec2-my-hng-devops-stage-0-experience-1mme</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;As part of the HNG DevOps internship program, I was tasked with configuring NGINX on a fresh Ubuntu server to serve a custom HTML page. This exercise tested my ability to set up a basic web server and reinforced my understanding of cloud infrastructure.  Let me share my journey with you.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Approach
&lt;/h2&gt;

&lt;p&gt;The task initially seemed straightforward: set up an NGINX web server on AWS to serve a custom HTML page. However, it quickly became apparent that this project would teach me valuable lessons about system administration and web server configuration. &lt;/p&gt;

&lt;p&gt;Here's how I broke down the task:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure Setup&lt;/strong&gt;&lt;br&gt;
I started by launching an &lt;strong&gt;Ubuntu-based EC2 instance&lt;/strong&gt; on AWS. The steps included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Choosing an &lt;strong&gt;Ubuntu 24.04 LTS AMI&lt;/strong&gt; (Free Tier eligible).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Selecting a &lt;strong&gt;t2.micro&lt;/strong&gt; instance (Free Tier eligible).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Creating a security group to &lt;strong&gt;allow SSH (port 22) and HTTP (port 80)&lt;/strong&gt; access from anywhere (0.0.0.0/0).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Generate an &lt;strong&gt;SSH key pair&lt;/strong&gt; to connect to my instance securely.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Connecting to the Instance and NGINX Installation and Configuration&lt;/strong&gt;&lt;br&gt;
After launching the EC2 instance, I used SSH to connect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chmod 400 your-key-pair.pem
ssh -i your-key-pair.pem ubuntu@your-instance-public-ip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, I updated the system and installed and configured NGINX:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Update package list
sudo apt update

# Install NGINX
sudo apt install nginx -y

# Verify NGINX is running
sudo systemctl status nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw2anpf0n047g03hkdp60.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw2anpf0n047g03hkdp60.png" alt="Image description" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Configuring the Custom HTML Page&lt;br&gt;
Next, I replaced the default index.html file with my custom page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo tee /var/www/html/index.html &amp;lt;&amp;lt; EOF
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;DevOps Stage 0&amp;lt;/title&amp;gt;
    &amp;lt;style&amp;gt;
        body {
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
            font-family: Arial, sans-serif;
            background-color: #f0f2f5;
        }
        .message {
            padding: 20px;
            background-color: white;
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }
    &amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;div class="message"&amp;gt;
        &amp;lt;h1&amp;gt;Welcome to DevOps Stage 0 - [Your Name]/[SlackName]&amp;lt;/h1&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
EOF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffffxykbyjguidipmhg9v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffffxykbyjguidipmhg9v.png" alt="Image description" width="800" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testing the Setup&lt;/strong&gt;&lt;br&gt;
I tested the NGINX configuration:&lt;br&gt;
&lt;code&gt;sudo nginx -t&lt;/code&gt;&lt;br&gt;
I restarted NGINX:&lt;br&gt;
&lt;code&gt;sudo systemctl restart nginx&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I opened a web browser and entered &lt;a href="http://ec2-public-ip/" rel="noopener noreferrer"&gt;http://ec2-public-ip/&lt;/a&gt;, confirming that my custom message was displayed.&lt;/p&gt;
&lt;h2&gt;
  
  
  Challenges and Solutions
&lt;/h2&gt;

&lt;p&gt;I encountered permission errors. Which I fixed allowing the appropriate permissions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Set correct ownership and permissions
sudo chown www-data:www-data /var/www/html/index.html
sudo chmod 644 /var/www/html/index.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyix6b6qwes4gigavu3ye.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyix6b6qwes4gigavu3ye.png" alt="Image description" width="800" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How This Task Contributes to My Learning and Career Goals
&lt;/h2&gt;

&lt;p&gt;Completing this task reinforced my understanding of cloud infrastructure, networking, and server administration. It was an essential step in my journey toward becoming a &lt;a href="https://hng.tech/hire/devops-engineers" rel="noopener noreferrer"&gt;DevOps Engineer&lt;/a&gt;. By working hands-on with &lt;strong&gt;AWS and NGINX&lt;/strong&gt;, I improved my troubleshooting skills and deepened my knowledge of cloud-based deployments. This experience also aligns with my long-term goal of mastering cloud automation and infrastructure management.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This experience gave me practical insights into server provisioning, firewall management, and web server configuration. The ability to set up a web server from scratch is a fundamental skill for &lt;a href="https://hng.tech/hire/cloud-engineers" rel="noopener noreferrer"&gt;Cloud Engineers&lt;/a&gt; and DevOps professionals. Moving forward, I plan to explore more advanced topics like load balancing, auto-scaling, and CI/CD pipelines.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>aws</category>
      <category>ec2</category>
      <category>hng</category>
    </item>
  </channel>
</rss>
