<?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: EphraimX</title>
    <description>The latest articles on DEV Community by EphraimX (@ephraimx).</description>
    <link>https://dev.to/ephraimx</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%2F637205%2Fec2ce089-2ae0-4c23-8bf7-e87e36134785.jpg</url>
      <title>DEV Community: EphraimX</title>
      <link>https://dev.to/ephraimx</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ephraimx"/>
    <language>en</language>
    <item>
      <title>How to Track and Manage Your Cloud Infrastructure Costs With Infracost</title>
      <dc:creator>EphraimX</dc:creator>
      <pubDate>Mon, 18 Aug 2025 02:36:36 +0000</pubDate>
      <link>https://dev.to/ephraimx/how-to-track-and-manage-your-cloud-infrastructure-costs-with-infracost-1djo</link>
      <guid>https://dev.to/ephraimx/how-to-track-and-manage-your-cloud-infrastructure-costs-with-infracost-1djo</guid>
      <description>&lt;p&gt;It's the beginning of a month, and you receive an email in your inbox detailing your AWS bill for the previous month. You see how much you're charged, and you're shocked. You begin to ask several questions: &lt;strong&gt;How did I spend this much?&lt;/strong&gt; &lt;strong&gt;What services did I use that cost this much?&lt;/strong&gt; &lt;strong&gt;Did I forget to run &lt;code&gt;terraform destroy&lt;/code&gt;?&lt;/strong&gt; Which you’re pretty sure you did… but the bill says otherwise. You sigh, still puzzled over how you ended up with such a huge charge.&lt;/p&gt;

&lt;p&gt;If this sounds familiar, you’re not alone. Whether you’re a beginner, a seasoned cloud engineer, or somewhere in between, almost everyone has faced this kind of surprise at least once. You can handle this surprise by using various cost monitoring tools, either provided by the cloud vendor or third parties, but in this article, we will focus on cost monitoring using &lt;strong&gt;Infracost&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Infracost
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.infracost.io/" rel="noopener noreferrer"&gt;&lt;strong&gt;Infracost&lt;/strong&gt;&lt;/a&gt; is a cost monitoring solution that helps teams and companies shift cost left. In simple terms, Infracost gives you visibility into the cost of your infrastructure before you deploy to the cloud, instead of waiting until the end of the month to see what your infrastructure costs you.&lt;/p&gt;

&lt;p&gt;This means you manage your cloud costs proactively rather than reactively. This allows you to balance your infrastructure needs with cost right from the onset, rather than deploying and waiting to find out your cost before attempting to optimize. And more importantly, this approach means you can make informed decisions, and this helps you to be more holistic in your approach, balancing both technical and business needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Use Infracost
&lt;/h2&gt;

&lt;p&gt;The major challenge with cloud costs is visibility. When working with cloud infrastructure, you often don't know how much something will cost until it's deployed; by then, it's already too late, as the resources are running, and the bill is counting.&lt;/p&gt;

&lt;p&gt;On reading this, you might say, you'll just make estimates using AWS or Azure calculator. And while you're right on this, it's a manual process and consumes valuable time that can be spent on activities much more profitable. Also, when using these tools, chances are you might leave out a resource or not account for a "hidden" charge. By hidden charge, I mean a charge that can go unnoticed.&lt;/p&gt;

&lt;p&gt;A good example of this would be in a previous project where I built an &lt;a href="https://dev.to/ephraimx/how-to-deploy-and-monitor-a-full-stack-application-using-docker-terraform-and-aws-2f7l"&gt;end-to-end DevOps pipeline&lt;/a&gt;. In this project, I needed to use a NAT gateway to allow the resources in the private subnets to connect to the internet. I did my research and saw what fees I would incur for including it in my architecture. Imagine my surprise when I saw my bill and discovered I would also have to pay for IPv4-related charges, which I did not initially account for.&lt;/p&gt;

&lt;p&gt;These reasons, among many others, are the reasons why Infracost matters. With Infracost, you see the cost of infrastructure ahead of time, so no surprises. You weigh tradeoffs between instance types, storage classes, or scaling strategies, allowing you to make smarter decisions. Lastly, costs show up in pull requests so everyone sees the financial impact of infrastructure changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started With Infracost
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Installing Infracost
&lt;/h3&gt;

&lt;p&gt;Installing Infracost depends on your operating system, but if you use a Linux operating system (like me), you can install Infracost by running the command below:&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="c"&gt;# Downloads the CLI based on your OS/arch and puts it in /usr/local/bin&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/infracost/infracost/master/scripts/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For other operating systems, you can find your respective commands &lt;a href="https://www.infracost.io/docs/#1-install-infracost" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating an Account on Infracost
&lt;/h3&gt;

&lt;p&gt;After installing Infracost, you need to create an account with Infracost. You can sign up &lt;a href="https://dashboard.infracost.io/" rel="noopener noreferrer"&gt;here&lt;/a&gt;, either with GitHub, Google, or email and password.&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%2F8hjlxfejzd6l7kj1rtlv.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%2F8hjlxfejzd6l7kj1rtlv.png" alt="Signup Here" width="800" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On signing up, select your "Source control integrations", depending on your Version Control System (VCS), and click on "Connect".&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%2Fbwvbiuy7ozrotrhabn7z.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%2Fbwvbiuy7ozrotrhabn7z.png" alt="Connect VCS" width="800" height="353"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You will be directed to install Infracost on your repository, either all or some. For security purposes, it is advisable to select only the repositories that you want Infracost to have access to. Once done, you confirm access by inputting your MFA code or password.&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%2F1nthmw2j6h5uqmmh77ub.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%2F1nthmw2j6h5uqmmh77ub.png" alt="Confirm GitHub" width="800" height="355"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;N.B.: If you do not have a Terraform project to practice with, you can fork an already existing Terraform project in my repo &lt;a href="https://github.com/EphraimX/end-to-end-devops-pipeline-using-aws-docker-and-terraform" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Back to Infracost, the next step is to decide how you want to "Add your code repos". You could choose to add all current and future Terraform repos automatically, which is recommended. You could choose to add it based on a wildcard, or finally add it manually. I'll go with the recommended version, but if you're hard on security, add your code repos manually.&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%2F9q7spv1e41ox6x6nvbls.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%2F9q7spv1e41ox6x6nvbls.png" alt="Ad Repo Mnaually" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the next sections, you'll see how to work with Infracost in pull requests and when working with Terraform.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Infracost Works in Pull Requests
&lt;/h2&gt;

&lt;p&gt;For this section, create a new branch in your repository; a test repo will work fine for this, or clone the repository linked above. Ensure this repository is accessible by Infracost.&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%2Fkmwkljfcae0v97hynkzq.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%2Fkmwkljfcae0v97hynkzq.png" alt="Infracosts in Pull Requests" width="800" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, create a file called &lt;code&gt;infracost_test.tf&lt;/code&gt; and paste in the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
  &lt;span class="nx"&gt;skip_credentials_validation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;skip_requesting_account_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;access_key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"mock_access_key"&lt;/span&gt;
  &lt;span class="nx"&gt;secret_key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"mock_secret_key"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"my_web_app"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ami&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ami-005e54dee72cc1d00"&lt;/span&gt;

  &lt;span class="nx"&gt;instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"m3.xlarge"&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; Try changing this to m5.xlarge to compare the costs&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"production"&lt;/span&gt;
    &lt;span class="nx"&gt;Service&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"web-app"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;root_block_device&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;volume_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; Try adding volume_type="gp3" to compare costs&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lambda_function"&lt;/span&gt; &lt;span class="s2"&gt;"my_hello_world"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;runtime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"nodejs12.x"&lt;/span&gt;
  &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"exports.test"&lt;/span&gt;
  &lt;span class="nx"&gt;image_uri&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"test"&lt;/span&gt;
  &lt;span class="nx"&gt;function_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"test"&lt;/span&gt;
  &lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arn:aws:ec2:us-east-1:123123123123:instance/i-1231231231"&lt;/span&gt;

  &lt;span class="nx"&gt;memory_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Prod"&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;p&gt;&lt;em&gt;N.B.: This code is from Infracost's official "Get Started"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Once created, commit your changes, and head to the branch's main page.&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%2Fel1gh9s1wqd19wfbtu62.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%2Fel1gh9s1wqd19wfbtu62.png" alt="Create a new feature" width="800" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, open a pull request by clicking on "Compare and Pull Request"  or "Contribute" on the right-hand side of the page.&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%2Fszlz5qgcybb67aehrnux.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%2Fszlz5qgcybb67aehrnux.png" alt="Compare or Push Request" width="800" height="348"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Put in your desired description and create your pull request&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%2Fdhq5870ggycld8g1naie.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%2Fdhq5870ggycld8g1naie.png" alt="Create Your Pull Requests" width="800" height="353"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On creating a pull request, Infracost scans your Terraform files and points out inconsistencies such as tagging policies and guardrails, as seen below.&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%2Fmz5zlo1vzfrp6lg3ol6u.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%2Fmz5zlo1vzfrp6lg3ol6u.png" alt="Terraform Repo Scan" width="800" height="355"&gt;&lt;/a&gt;&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%2Fmym2qdp4g5xze108md6r.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%2Fmym2qdp4g5xze108md6r.png" alt="Terraform Repo Scan" width="800" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How Infracost Works with Terraform
&lt;/h2&gt;

&lt;p&gt;For this section, create or clone a repository with Terraform files available, and ensure you have Terraform installed on your local machine.&lt;/p&gt;

&lt;p&gt;To start, change into your project's root directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd end-to-end-devops-pipeline-using-aws-docker-and-terraform
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, get your Infracost API key. If you don't have it, you can generate it by clicking "Settings", then clicking on "Org Settings".&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%2F9kek989ytqtz77pc92iw.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%2F9kek989ytqtz77pc92iw.png" alt="API KEY" width="800" height="398"&gt;&lt;/a&gt;&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%2Fwu49tqi9rnzjahvm4bqh.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%2Fwu49tqi9rnzjahvm4bqh.png" alt="INFRACOST API KEY" width="800" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, to get your API, copy the already generated token for CLI and CI/CD operations.&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%2F0aletde4dhmjnf7zaer9.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%2F0aletde4dhmjnf7zaer9.png" alt="COPY API KEY" width="800" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After getting your API key, the next step is to configure your CLI. You can do this 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;infracost configure &lt;span class="nb"&gt;set &lt;/span&gt;api_key &lt;span class="o"&gt;{&lt;/span&gt;API_KEY&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;{API_KEY}&lt;/code&gt; with your actual API key.&lt;/p&gt;

&lt;p&gt;Once completed, you can get your Terraform infrastructure costs 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;infracost breakdown &lt;span class="nt"&gt;--path&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here's the result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Project: main

 Name                                                          Monthly Qty  Unit                        Monthly Cost   

 aws_instance.my_web_app                                                                                               
 ├─ Instance usage (Linux/UNIX, on-demand, m3.xlarge)                  730  hours                            $194.18   
 └─ root_block_device                                                                                                  
    └─ Storage (general purpose SSD, gp2)                            1,000  GB                               $100.00   

 aws_lambda_function.my_hello_world                                                                                    
 ├─ Requests                                           Monthly cost depends on usage: $0.20 per 1M requests            
 ├─ Ephemeral storage                                  Monthly cost depends on usage: $0.0000000309 per GB-seconds     
 └─ Duration (first 6B)                                Monthly cost depends on usage: $0.0000166667 per GB-seconds     

 Project total                                                                                               $294.18   

──────────────────────────────────
Project: terraform
Module path: terraform

 Name                                                                   Monthly Qty  Unit              Monthly Cost   

 aws_instance.roi_calculator_bastion_host_ec2_public_subnet_one                                                       
 ├─ Instance usage (Linux/UNIX, on-demand, t3.xlarge)                           730  hours                  $121.47   
 └─ root_block_device                                                                                                 
    └─ Storage (general purpose SSD, gp2)                                         8  GB                       $0.80   

 aws_instance.roi_calculator_bastion_host_ec2_public_subnet_two                                                       
 ├─ Instance usage (Linux/UNIX, on-demand, t3.xlarge)                           730  hours                  $121.47   
 └─ root_block_device                                                                                                 
    └─ Storage (general purpose SSD, gp2)                                         8  GB                       $0.80   

 aws_instance.roi_calculator_production_host_ec2_private_subnet_one                                                   
 ├─ Instance usage (Linux/UNIX, on-demand, t3.xlarge)                           730  hours                  $121.47   
 └─ root_block_device                                                                                                 
    └─ Storage (general purpose SSD, gp2)                                         8  GB                       $0.80   

 aws_instance.roi_calculator_production_host_ec2_private_subnet_two                                                   
 ├─ Instance usage (Linux/UNIX, on-demand, t3.xlarge)                           730  hours                  $121.47   
 └─ root_block_device                                                                                                 
    └─ Storage (general purpose SSD, gp2)                                         8  GB                       $0.80   

 aws_nat_gateway.roi_calculator_ngw_private_subnet_one                                                                
 ├─ NAT gateway                                                                 730  hours                   $32.85   
 └─ Data processed                                                   Monthly cost depends on usage: $0.045 per GB     

 aws_nat_gateway.roi_calculator_ngw_private_subnet_two                                                                
 ├─ NAT gateway                                                                 730  hours                   $32.85   
 └─ Data processed                                                   Monthly cost depends on usage: $0.045 per GB     

 aws_lb.roi_calculator_aws_lb                                                                                         
 ├─ Application load balancer                                                   730  hours                   $16.43   
 └─ Load balancer capacity units                                     Monthly cost depends on usage: $5.84 per LCU     

 aws_db_instance.roi_calculator                                                                                       
 ├─ Database instance (on-demand, Single-AZ, db.t3.micro)                       730  hours                   $13.14   
 └─ Storage (general purpose SSD, gp2)                                            5  GB                       $0.58   

 Project total                                                                                              $584.93   

 OVERALL TOTAL                                                                                             $879.11 

*Usage costs can be estimated by updating Infracost Cloud settings; see docs for other options.

──────────────────────────────────
49 cloud resources were detected:
∙ 10 were estimated
∙ 39 were free

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Project                                            ┃ Baseline cost ┃ Usage cost* ┃ Total cost ┃
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━╋━━━━━━━━━━━━┫
┃ main                                               ┃          $294 ┃           - ┃       $294 ┃
┃ terraform                                          ┃          $585 ┃           - ┃       $585 ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━┻━━━━━━━━━━━━┛
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the example above, you see that Infracost breakdown estimates $879/month, mostly from EC2 instances ($680) and NAT gateways ($65), with smaller costs from storage, RDS, and load balancers. This clearly shows how compute and networking dominate cloud bills in this setup, and why Infracost is useful for spotting optimizations like right-sizing instances or reducing NAT gateways before deploying.&lt;/p&gt;

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

&lt;p&gt;Getting hit with an unexpected cloud bill is one of those experiences that stays with you, and not in a good way. Throughout this article, we've explored how Infracost can transform the way you handle cloud costs by giving you the information you need upfront, rather than leaving you to discover it in your monthly bill. The beauty of Infracost lies in its simplicity; you get real-time cost estimates integrated right into your development workflow, whether through pull requests or command-line breakdowns.&lt;/p&gt;

&lt;p&gt;The shift from reactive to proactive cost management isn't just about saving money; it's about making better decisions with complete information. Tools like Infracost help ensure that cost visibility becomes a natural part of your workflow, balancing both technical requirements and business realities from day one. At the end of the day, the best way to avoid bill shock is to eliminate the shock entirely.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Dev, Test, Stage, Prod: How Applications Are Deployed in the Real World</title>
      <dc:creator>EphraimX</dc:creator>
      <pubDate>Thu, 14 Aug 2025 09:08:02 +0000</pubDate>
      <link>https://dev.to/ephraimx/dev-test-stage-prod-how-applications-are-deployed-in-the-real-world-5c28</link>
      <guid>https://dev.to/ephraimx/dev-test-stage-prod-how-applications-are-deployed-in-the-real-world-5c28</guid>
      <description>&lt;p&gt;Many developers and DevOps engineers begin building their projects in a similar manner. They have a project idea, create a GitHub or GitLab repository, and then they get to building out the project. This process is ideal when you're a beginner or working on a side project that you plan to present to recruiters to increase your chances of getting hired.&lt;/p&gt;

&lt;p&gt;However, this process is not sufficient when building for end-users and deploying in production, as you will immediately notice upon being onboarded to the company's version control system. You get to see a standard way of shipping to production, and if this is your first time seeing this, you might be wondering what's going on.&lt;/p&gt;

&lt;p&gt;That's why I'm writing this article. The goal is to present a high-level explanation of how startups and organizations deploy their code to production, which is to be used by their end-users. If you're just starting in your career or you know how to code but haven't worked in this sort of environment, this article is for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dev, Test, Stage, Prod: What Do They Mean?
&lt;/h2&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%2F5e6agdsgnfmzpfk54lvk.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%2F5e6agdsgnfmzpfk54lvk.png" alt="Dev, Test, Stage, Prod: What Do They Mean?" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From the article topic, you might have guessed or known that dev stands for development, test for testing, stage for staging, and prod for production. So what are they? They are environments. And what is an environment in this context? An environment is a space that is built and tailored to serve a specific interest or set of interests. In this case, the interest of ensuring your code or application is built and deployed to the highest standard possible.&lt;/p&gt;

&lt;p&gt;When building applications for thousands to millions to billions of people, each developer cannot just build on their local machine, push to GitHub, merge it (that's if it does), and push straight to production. This is a true and tested recipe for chaos, as you can imagine. What you get from this is conflicting, unstandardized code and production environments. This inevitably leads to a terrible product, a bad reputation, and a declining customer base. That's why no one does it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dev, Test, Stage, Prod: Who Uses What and Why?
&lt;/h2&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%2Fag6j3zwhd9a1zsxa5v4i.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%2Fag6j3zwhd9a1zsxa5v4i.png" alt="Dev, Test, Stage, Prod: Who Uses What and Why?" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One thing to keep in mind is that this setup changes from company to company. So don't expect to see this setup everywhere you go. What you will find is that some companies use just Dev and Prod, some Test and Prod, others Dev, Stage, Prod, and others commit to the full process. &lt;/p&gt;

&lt;p&gt;What is generally seen is that startups tend to favour the Dev, Stage, Prod method, where speed is prioritized over process. They get less maintenance overhead as there are fewer environments, and it works well when deployments are frequent, and also, teams can quickly rollback if needed. Also, for this setup, developers can do some testing locally or in the staging environment before deploying to production.&lt;/p&gt;

&lt;p&gt;On the other hand, companies that are mid to large in terms of size, work in regulated industries like finance, healthcare, aerospace, or the military, and have dedicated QA teams, go for the Dev, Test, Stage, and Prod environment setup. Why? The first reason is that more layers between development and production reduce the risk of pushing untested code to production. Also, companies in these categories mostly prioritize process over speed, and they like to get it right once, no matter how long it takes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dev, Test, Stage, Prod: Features And Differences
&lt;/h2&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%2Fmc2jodg0390jcidnnq6f.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%2Fmc2jodg0390jcidnnq6f.png" alt="Dev, Test, Stage, Prod: Features And Differences" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here, you'll understand the details behind these various environments and what they entail, starting with the development environment&lt;/p&gt;

&lt;h3&gt;
  
  
  Development Environment
&lt;/h3&gt;

&lt;p&gt;This is the initial environment where developers first check in their new code. It's primarily for developers to see if their code changes are working on a basic level. This environment is very messy, often corrupted, and goes down a lot due to developers frequently checking in new code, and they sometimes do this without any testing or just minimal tests. In summary, little to no testing goes on in this environment&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing Environment
&lt;/h3&gt;

&lt;p&gt;The test environment is where the code goes after some initial checks take place in Dev, and it's the primary place for Quality Assurance and testing activities. Unlike the dev environment, it's a more controlled environment for functional, integration, and regression testing. &lt;/p&gt;

&lt;p&gt;This environment is usually used by dedicated QA teams within these organizations. It's more like the quality control department of the code process, where tests like smoke tests, integration tests, regression tests, automated tests, and all manual tests are carried out. Any bug found within this environment is reported back to the dev team, and the code is tagged so that defects can be traced to specific builds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pre-prod or Staging Environment
&lt;/h3&gt;

&lt;p&gt;The staging or pre-production environment is a near-identical replica of production for final acceptance testing. By final acceptance testing, I mean this is the last checkpoint before software or a system goes live. &lt;/p&gt;

&lt;p&gt;It generally involves a formal review process where stakeholders like business owners, product managers, or beta testers verify that the product meets the agreed requirements and specifications. In the two previous stages, the focus was mainly on the technical aspect of the product; in this stage, the focus is on ensuring it works in real-world scenarios.&lt;/p&gt;

&lt;p&gt;As mentioned initially, the staging environment is very similar to the production environment. It uses the same size machines, same hardware specs, software versions, configurations, integrations, and sometimes even the production database, just like the production environment. You also find that load and stress tests occur in this stage to mimic live production usage. All this is done to ensure the system doesn't fail in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  Production Environment
&lt;/h3&gt;

&lt;p&gt;This is the final environment where the code is live and accessible to the public. It is the version that your customers interact with. In terms of stability, it is the most stable environment, as any issues can have severe consequences. The code in this environment is a result of thorough checks and is constantly monitored for any issues that may come up.&lt;/p&gt;

&lt;p&gt;In this stage, you validate if the deployment was successful, but compared to other stages, you do not run as many tests, and tests here are generally limited to non-destructive tests (like loading a page without changing data), as creating fake users or generating fake data can interfere with analytics or violate security/government controls. At this final stage, scaling, caching, and reliability are the top priorities of the organization. At this point, the deployment pipeline comes to an end.&lt;/p&gt;

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

&lt;p&gt;I hope this article aided your understanding of what deployment looks like in the real world. If you're like me, who was previously confused about what these stages mean, I hope this article clears up your doubt. If you need more resources, you can check out the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=J7X1iE3LEzs&amp;amp;pp=ygUOZGV2IHN0YWdlIHByb2Q%3D" rel="noopener noreferrer"&gt;Understanding Deployment Environments: Dev, Test, Staging &amp;amp; Production Explained!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=A1E6P7U_nrk&amp;amp;pp=ygUOZGV2IHN0YWdlIHByb2Q%3D" rel="noopener noreferrer"&gt;Staging vs Production: How Tech Startups Deploy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=WUra9ugnVhs&amp;amp;pp=ygUOZGV2IHN0YWdlIHByb2TSBwkJrQkBhyohjO8%3D" rel="noopener noreferrer"&gt;CI/CD Workflow from Dev to Stage to Prod Environments&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thank you for reading to the end, and if you're interested, you can read more DevOps-related content on my &lt;a href="https://dev.to/ephraimx"&gt;page&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>development</category>
      <category>devops</category>
      <category>discuss</category>
      <category>git</category>
    </item>
    <item>
      <title>End-to-End DevOps: Deploying and Monitoring a Full-Stack App with Docker, Terraform, and AWS</title>
      <dc:creator>EphraimX</dc:creator>
      <pubDate>Wed, 13 Aug 2025 14:25:20 +0000</pubDate>
      <link>https://dev.to/ephraimx/how-to-deploy-and-monitor-a-full-stack-application-using-docker-terraform-and-aws-2f7l</link>
      <guid>https://dev.to/ephraimx/how-to-deploy-and-monitor-a-full-stack-application-using-docker-terraform-and-aws-2f7l</guid>
      <description>&lt;p&gt;In this article, you’ll learn how to build and deploy a production-ready full-stack application on AWS using Docker, Terraform, Prometheus, and Grafana for containerization, infrastructure provisioning, monitoring, and visualizing application metrics, respectively.&lt;/p&gt;

&lt;p&gt;This project reflects how full-stack applications are deployed in real-world environments, from startups to large organizations. It’s ideal for full-stack developers looking to deploy their apps to the cloud, or aspiring DevOps engineers who want a hands-on understanding of how infrastructure, deployment, and monitoring all come together.&lt;/p&gt;

&lt;p&gt;By the end of this article, you’ll walk away with both the practical skills and theoretical knowledge needed to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Containerize a full-stack app&lt;/li&gt;
&lt;li&gt;Provision and manage cloud infrastructure using Terraform&lt;/li&gt;
&lt;li&gt;Deploy services to AWS&lt;/li&gt;
&lt;li&gt;Set up full observability using Prometheus and Grafana&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites and Setup
&lt;/h2&gt;

&lt;p&gt;Before you begin, ensure you have the following tools and resources configured on your local machine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Docker &amp;amp; Docker Compose&lt;/strong&gt;
These are required to containerize and run the application services locally before deployment. Follow the official installation guide based on your operating system &lt;a href="https://medium.com/@piyushkashyap045/comprehensive-guide-installing-docker-and-docker-compose-on-windows-linux-and-macos-a022cf82ac0b" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terraform&lt;/strong&gt;
Terraform will be used to provision all necessary infrastructure on AWS. You can install it by following the instructions on the &lt;a href="https://developer.hashicorp.com/terraform/downloads" rel="noopener noreferrer"&gt;official HashiCorp website&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS CLI&lt;/strong&gt;
Required to configure and authenticate your AWS credentials locally. Install it using the official &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html" rel="noopener noreferrer"&gt;AWS CLI installation guide&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Account + Access Keys&lt;/strong&gt;
You’ll need an AWS account with programmatic access enabled. If you don’t have one, &lt;a href="https://signin.aws.amazon.com/signup?request_type=register" rel="noopener noreferrer"&gt;sign up here&lt;/a&gt;. Then generate your &lt;strong&gt;Access Key ID&lt;/strong&gt; and &lt;strong&gt;Secret Access Key&lt;/strong&gt; by following &lt;a href="https://www.msp360.com/resources/blog/how-to-find-your-aws-access-key-id-and-secret-access-key/" rel="noopener noreferrer"&gt;this step-by-step guide&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Git and Project Repository&lt;/strong&gt;
Clone the GitHub repository containing all the code and infrastructure configuration for this project:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  git clone https://github.com/EphraimX/aws-devops-fullstack-pipeline.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once all tools are installed and your credentials are set, you're ready to begin deploying and monitoring the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the Application
&lt;/h2&gt;

&lt;p&gt;The application you're deploying is a classic &lt;strong&gt;three-tier architecture&lt;/strong&gt; consisting of:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Frontend (Client-side)&lt;/strong&gt; – A simple interface that collects user input.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend (Server-side)&lt;/strong&gt; – A FastAPI application that handles computation and database operations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database&lt;/strong&gt; – A PostgreSQL instance hosted on AWS RDS.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The application functions as an &lt;strong&gt;ROI (Return on Investment) calculator&lt;/strong&gt;. It accepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The total cost of an investment,&lt;/li&gt;
&lt;li&gt;The expected monthly profit, and&lt;/li&gt;
&lt;li&gt;The number of months the profit is expected to come in.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once the user submits these values, the frontend sends a request to the backend via the &lt;code&gt;/api/calculate-roi&lt;/code&gt; endpoint. The backend calculates the ROI percentage and the time to break even, then sends the result back to the frontend.&lt;/p&gt;

&lt;p&gt;Here’s the logic behind the ROI calculation:&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RoiCalculationRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;revenue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;timeHorizon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;

&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/calculate-roi&lt;/span&gt;&lt;span class="sh"&gt;"&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;calculate_roi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RoiCalculationRequest&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Calculates Return on Investment (ROI) and break-even time.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;current_cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;
    &lt;span class="n"&gt;current_revenue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;revenue&lt;/span&gt;
    &lt;span class="n"&gt;current_time_horizon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeHorizon&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;current_cost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current_revenue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current_time_horizon&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;current_time_horizon&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&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="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Please enter valid numbers for all fields.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;total_revenue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current_revenue&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;current_time_horizon&lt;/span&gt;
    &lt;span class="n"&gt;net_profit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;total_revenue&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;current_cost&lt;/span&gt;

    &lt;span class="n"&gt;calculated_roi_percent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current_cost&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;calculated_roi_percent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;net_profit&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;current_cost&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;net_profit&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;calculated_roi_percent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;inf&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;calculated_break_even_months&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;N/A&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current_revenue&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;calculated_break_even_months&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_cost&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;current_revenue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;current_cost&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;calculated_break_even_months&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Never&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;calculated_break_even_months&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;roi_percent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Infinite&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;calculated_roi_percent&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;inf&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;calculated_roi_percent&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;break_even_months&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;calculated_break_even_months&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;roiPercent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;roi_percent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;breakEvenMonths&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;break_even_months&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the frontend receives the ROI and break-even results, it makes another API request to &lt;code&gt;/api/recordEntry&lt;/code&gt;. This second endpoint records both the &lt;strong&gt;user inputs&lt;/strong&gt; and the &lt;strong&gt;computed results&lt;/strong&gt; into the PostgreSQL database for tracking and analysis.&lt;/p&gt;

&lt;p&gt;Here’s the relevant backend logic:&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PaymentRecordRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;revenue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;time_horizon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(...,&lt;/span&gt; &lt;span class="n"&gt;alias&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timeHorizon&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;roi_percent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(...,&lt;/span&gt; &lt;span class="n"&gt;alias&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;roiPercent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;break_even_months&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(...,&lt;/span&gt; &lt;span class="n"&gt;alias&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;breakEvenMonths&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;

    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;validate_by_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt; 

&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/recordEntry&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;record_payment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PaymentRecordRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_db&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;new_record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PaymentRecord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;revenue&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;revenue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;time_horizon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time_horizon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;roi_percent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;roi_percent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;break_even_months&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;break_even_months&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;
        &lt;span class="p"&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;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_record&lt;/span&gt;&lt;span class="p"&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;commit&lt;/span&gt;&lt;span class="p"&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;refresh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&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;Payment record saved to RDS.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&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="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Failed to save payment record.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Below is a short demo that shows the flow of the application in action:&lt;/p&gt;



&lt;h2&gt;
  
  
  Application Containerization
&lt;/h2&gt;

&lt;p&gt;Containerization allows developers to build and ship their applications on any machine or server without running into a myriad of issues like installation errors or dependency problems. It creates its operating system for applications to run on.&lt;/p&gt;

&lt;p&gt;The process of containerizing an application depends mainly on how the application runs, the packages it needs, and the overall architecture of your system. For this application, the frontend tier runs on Next.js, and the backend runs on FastAPI.&lt;/p&gt;

&lt;p&gt;For the frontend tier, here's the Dockerfile for it:&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="c"&gt;# ---------- Builder Stage ----------&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:21.5-bullseye-slim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /usr/src/app&lt;/span&gt;

&lt;span class="c"&gt;# Copy package files and install dependencies&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--force&lt;/span&gt;

&lt;span class="c"&gt;# Copy application code&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="c"&gt;# Build-time environment variable&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; NEXT_PUBLIC_APIURL&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NEXT_PUBLIC_APIURL=$NEXT_PUBLIC_APIURL&lt;/span&gt;

&lt;span class="c"&gt;# Build the app&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="c"&gt;# ---------- Production Stage ----------&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:21.5-bullseye-slim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;runner&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /usr/src/app&lt;/span&gt;

&lt;span class="c"&gt;# Install only production dependencies&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--omit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dev &lt;span class="nt"&gt;--force&lt;/span&gt;

&lt;span class="c"&gt;# Install PM2 globally&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; pm2

&lt;span class="c"&gt;# Copy built app from builder stage&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/src/app/.next ./.next&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/src/app/public ./public&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/src/app/node_modules ./node_modules&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/src/app/package.json ./package.json&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/src/app/next.config.mjs ./next.config.mjs&lt;/span&gt;

&lt;span class="c"&gt;# Expose app port&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;

&lt;span class="c"&gt;# Set runtime env (can override with --env at runtime)&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NEXT_PUBLIC_APIURL=$NEXT_PUBLIC_APIURL&lt;/span&gt;

&lt;span class="c"&gt;# Start Next.js using PM2&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["pm2-runtime", "start", "npm", "--", "start"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the Dockerfile above, you can see you have two stages: the builder stage and the production stage. Dockerfiles like this are referred to as &lt;a href="https://www.geeksforgeeks.org/devops/what-is-an-multistage-dockerfile/" rel="noopener noreferrer"&gt;multi-stage Dockerfiles&lt;/a&gt;, and they're important because they help reduce the size of the image and make it more secure by using only the necessary components of the application, thereby reducing the total surface area for an attack.&lt;/p&gt;

&lt;p&gt;In the builder stage of the frontend Dockerfile, you start by defining the base image, which in this case is an operating system with a version of Node.js installed. Next, you define the working directory and copy all variations of the &lt;code&gt;package.json&lt;/code&gt; file, before running &lt;code&gt;npm install --force&lt;/code&gt; to force install all the packages. You then set the API URL as a build-time argument and set it as an environment variable that the application can reference during build. Finally, you run &lt;code&gt;npm run build&lt;/code&gt; to build the application.&lt;/p&gt;

&lt;p&gt;Once that's done, you move to the production stage, where you choose a similar base image and working directory. You then copy all variations of the &lt;code&gt;package.json&lt;/code&gt; file again and run &lt;code&gt;npm install --omit=dev --force&lt;/code&gt; to install only production dependencies. Next, you install &lt;code&gt;pm2&lt;/code&gt;, which is what you'll use to serve the application. You then copy all the relevant folders from the previous stage into your working directory, expose port &lt;code&gt;3000&lt;/code&gt;, set your runtime environment variable, and pass in the entrypoint command to start the application with PM2.&lt;/p&gt;

&lt;p&gt;Once this is done, you can move on to your backend Dockerfile. Here, you follow the same principle of a multi-stage Dockerfile, for the added reasons of security and image size efficiency.&lt;/p&gt;

&lt;p&gt;Here's the Dockerfile for the backend system:&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="c"&gt;# =================================&lt;/span&gt;
&lt;span class="c"&gt;# Stage 1: Base Dependencies&lt;/span&gt;
&lt;span class="c"&gt;# =================================&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;python:3.11-slim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;

&lt;span class="c"&gt;# Install system dependencies&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    curl &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# =================================&lt;/span&gt;
&lt;span class="c"&gt;# Stage 2: Dependencies Builder&lt;/span&gt;
&lt;span class="c"&gt;# =================================&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;deps-builder&lt;/span&gt;

&lt;span class="c"&gt;# Install build dependencies&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    build-essential &lt;span class="se"&gt;\
&lt;/span&gt;    libpq-dev &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="c"&gt;# Create virtual environment&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; venv /venv
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PATH="/venv/bin:$PATH"&lt;/span&gt;

&lt;span class="c"&gt;# Install Python dependencies&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;--upgrade&lt;/span&gt; pip &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&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="c"&gt;# =================================&lt;/span&gt;
&lt;span class="c"&gt;# Stage 3: Production&lt;/span&gt;
&lt;span class="c"&gt;# =================================&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;production&lt;/span&gt;

&lt;span class="c"&gt;# Install runtime dependencies only&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    libpq5 &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; groupadd &lt;span class="nt"&gt;-r&lt;/span&gt; fastapi &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; useradd &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; fastapi fastapi

&lt;span class="c"&gt;# Copy virtual environment&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=deps-builder /venv /venv&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PATH="/venv/bin:$PATH"&lt;/span&gt;

&lt;span class="c"&gt;# Copy application&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . /app/&lt;/span&gt;

&lt;span class="c"&gt;# Make scripts executable&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /app/scripts/entrypoint.sh
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /app/scripts/healthcheck.sh

&lt;span class="c"&gt;# Set ownership&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; fastapi:fastapi /app

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; fastapi&lt;/span&gt;

&lt;span class="c"&gt;# Health check&lt;/span&gt;
&lt;span class="k"&gt;HEALTHCHECK&lt;/span&gt;&lt;span class="s"&gt; --interval=30s --timeout=10s --start-period=5s --retries=3 \&lt;/span&gt;
    CMD /app/scripts/healthcheck.sh

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8000&lt;/span&gt;

&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["/app/scripts/entrypoint.sh"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From your backend Dockerfile, you have 3 stages. The first stage is where you install the base dependencies, more like the fundamental packages needed for the image OS to run properly. You also set the working directory in this stage.&lt;/p&gt;

&lt;p&gt;Next is the dependencies builder stage, where you use the base stage as your image and install the Python dependencies. The last stage is the production stage, where you copy the environment packages from the &lt;code&gt;deps&lt;/code&gt; stage, set your Python path to &lt;code&gt;/venv/bin&lt;/code&gt;, and copy the entire application to your &lt;code&gt;/app&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;Next, you make both the entrypoint and healthcheck scripts executable. The healthcheck script is what your Dockerfile runs to ensure the backend system is working as expected.&lt;/p&gt;

&lt;p&gt;Here's the content of the file:&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="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-x&lt;/span&gt;

&lt;span class="c"&gt;# Check application viability&lt;/span&gt;
curl &lt;span class="nt"&gt;--fail&lt;/span&gt; http://localhost:8000/api/healthcheck &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1

&lt;span class="c"&gt;# Check database viability&lt;/span&gt;
curl &lt;span class="nt"&gt;--fail&lt;/span&gt; http://localhost:8000/api/dbHealth &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here you set &lt;code&gt;-e&lt;/code&gt; for the script to cancel if any command fails, and also &lt;code&gt;-x&lt;/code&gt; to print the processes as they run. Then you make requests to both the application healthcheck and the database healthcheck endpoints to ensure the main components of the backend infrastructure are running as required.&lt;/p&gt;

&lt;p&gt;Once that’s done, you set &lt;code&gt;fastapi&lt;/code&gt; as the owner of the system, switch to that user, and run the healthcheck script as described above. Finally, you expose port 8000 and then run the entrypoint script.&lt;/p&gt;

&lt;p&gt;Here’s the entrypoint script:&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="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-x&lt;/span&gt;

alembic &lt;span class="nt"&gt;-c&lt;/span&gt; /app/alembic.ini upgrade &lt;span class="nb"&gt;head

exec &lt;/span&gt;uvicorn main:app &lt;span class="nt"&gt;--host&lt;/span&gt; 0.0.0.0 &lt;span class="nt"&gt;--port&lt;/span&gt; 8000 &lt;span class="nt"&gt;--workers&lt;/span&gt; 4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similar to the healthcheck script, you set it to fail on error and print logs as the application runs. Then you run migrations using Alembic to essentially create the tables in your PostgreSQL database, before starting the FastAPI application on host &lt;code&gt;0.0.0.0&lt;/code&gt; and port &lt;code&gt;8000&lt;/code&gt;, with 4 workers to run concurrent processes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring Setup With Prometheus and Grafana
&lt;/h2&gt;

&lt;p&gt;Monitoring is an important aspect of every application structure. It is often said that &lt;em&gt;you can’t improve what you don’t measure&lt;/em&gt;, hence why monitoring is a core part of every application that makes it to production.&lt;/p&gt;

&lt;p&gt;One thing to note is that there are two major components of monitoring: &lt;strong&gt;logs&lt;/strong&gt; and &lt;strong&gt;metrics&lt;/strong&gt;. Logs are information collected or inputted by the different services that are running, while metrics are thresholds that are generally measured from actions or events.&lt;/p&gt;

&lt;p&gt;There are different tools used within the industry to monitor systems, but two of the most popular tools used in tandem are &lt;strong&gt;Prometheus&lt;/strong&gt; and &lt;strong&gt;Grafana&lt;/strong&gt;. Prometheus is a monitoring application that collects information and stores it in a central database. It gets this information by pulling and scraping metrics from different targets and servers.&lt;/p&gt;

&lt;p&gt;Grafana, on the other hand, is a visualization platform that uses &lt;strong&gt;PromQL&lt;/strong&gt; (Prometheus Query Language) to query this information from Prometheus. This queried data can be visualized via dashboards, either created by the engineer or sourced from the community. It's generally advisable to first search if the dashboard you need already exists on the &lt;a href="https://grafana.com/grafana/dashboards/" rel="noopener noreferrer"&gt;Grafana Community Dashboards&lt;/a&gt; before going ahead to build a custom solution.&lt;/p&gt;

&lt;p&gt;For this article, you will monitor two important sets of metrics. First, you will monitor the &lt;strong&gt;host machine metrics&lt;/strong&gt; using &lt;code&gt;node_exporter&lt;/code&gt;, and also monitor &lt;strong&gt;container resources&lt;/strong&gt; using &lt;code&gt;cadvisor&lt;/code&gt;. Monitoring these two gives you a perspective into how your system is performing. With &lt;code&gt;node_exporter&lt;/code&gt;, you get to see how your host machine is behaving, how your CPU and RAM are responding to the system’s workload. With &lt;code&gt;cadvisor&lt;/code&gt;, you monitor your container resources to keep an eye on how they’re performing with the applications running in them.&lt;/p&gt;

&lt;p&gt;To get started with monitoring, navigate to the &lt;code&gt;monitoring&lt;/code&gt; folder and take a look at the &lt;code&gt;monitoring-docker-compose.yml&lt;/code&gt; file, as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;prometheus-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;local&lt;/span&gt;
  &lt;span class="na"&gt;grafana-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;local&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;monitoring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&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;prometheus&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;prom/prometheus: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;prometheus&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;9090:9090"&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="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./prometheus.yaml:/etc/prometheus/prometheus.yml"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prometheus-data:/prometheus"&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;command&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;--config.file=/etc/prometheus/prometheus.yml"&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;monitoring&lt;/span&gt;

  &lt;span class="na"&gt;grafana&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;grafana/grafana: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;grafana&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;3000:3000"&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="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;grafana-data:/var/lib/grafana"&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;monitoring&lt;/span&gt;

  &lt;span class="na"&gt;cadvisor&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;gcr.io/cadvisor/cadvisor:v0.52.1&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;cadvisor&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;8085:8080"&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;/:/rootfs:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/run:/run:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/sys:/sys:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/var/lib/docker/:/var/lib/docker:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/dev/disk/:/dev/disk:ro&lt;/span&gt;
    &lt;span class="na"&gt;devices&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/dev/kmsg&lt;/span&gt;
    &lt;span class="na"&gt;privileged&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;monitoring&lt;/span&gt;

  &lt;span class="na"&gt;node_exporter&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;quay.io/prometheus/node-exporter:v1.9.1&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;node_exporter&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--path.rootfs=/host"&lt;/span&gt;
    &lt;span class="na"&gt;pid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;host&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&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;/:/host:ro,rslave&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;monitoring&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For your monitoring file, you start out by defining the volumes for both Prometheus and Grafana, as these services require persistent volumes to store configurations and data. Next, you define the different services, including the image, container name, the host, and container ports, volumes, and startup commands where necessary.&lt;/p&gt;

&lt;p&gt;For Prometheus to know what services to scrape, they need to be included as a target in the &lt;code&gt;prometheus.yml&lt;/code&gt; file, which you can see below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;global&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;scrape_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;15s&lt;/span&gt;  

&lt;span class="na"&gt;scrape_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Monitor Prometheus&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;prometheus'&lt;/span&gt;
    &lt;span class="na"&gt;scrape_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
    &lt;span class="na"&gt;static_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;localhost:9090'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="c1"&gt;# Monitor EC2 Instances&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;node_exporter'&lt;/span&gt;
    &lt;span class="na"&gt;static_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;node_exporter:9100'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="c1"&gt;# Monitor All Docker Containers&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cadvisor'&lt;/span&gt;
    &lt;span class="na"&gt;static_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cadvisor:8085'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, you set Prometheus up to scrape its own metrics on port 9090, &lt;code&gt;node_exporter&lt;/code&gt; on port 9100, and &lt;code&gt;cadvisor&lt;/code&gt; on port 8085&lt;/p&gt;

&lt;p&gt;Once this is done, you can now build and start your monitoring stack 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;&lt;span class="nb"&gt;sudo &lt;/span&gt;docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; monitoring-docker-compose.yml up &lt;span class="nt"&gt;-d&lt;/span&gt; prometheus cadvisor node_exporter grafana
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once successful, you should find a similar printout in your terminal:&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="o"&gt;[&lt;/span&gt;+] Running 30/30
 ✔ node_exporter Pulled                                                  132.6s 
 ✔ prometheus Pulled                                                     129.6s 
 ✔ cadvisor Pulled                                                        20.9s 
 ✔ grafana Pulled                                                        129.4s 


&lt;span class="o"&gt;[&lt;/span&gt;+] Running 7/7
 ✔ Network monitoring_default           Cr...                              0.1s 
 ✔ Volume &lt;span class="s2"&gt;"monitoring_prometheus-data"&lt;/span&gt;  Created                            0.0s 
 ✔ Volume &lt;span class="s2"&gt;"monitoring_grafana-data"&lt;/span&gt;     Created                            0.0s 
 ✔ Container cadvisor                   Started                           13.6s 
 ✔ Container grafana                    Started                           13.6s 
 ✔ Container node_exporter              Start...                          13.3s 
 ✔ Container prometheus                 Started                           13.4s 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once your monitoring services are running, the next step is to set up your Grafana dashboard. You can do this by heading to &lt;code&gt;localhost:3000&lt;/code&gt;, where you’ll be met with the Grafana login page. Proceed to log in with the default username and password: &lt;code&gt;admin&lt;/code&gt;. For security purposes, it is advisable to change this on first login.&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%2Fqm2n8fu2ilpqqjflra99.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%2Fqm2n8fu2ilpqqjflra99.png" alt=" " width="800" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once logged in, the first step would be to set Prometheus as your data source:&lt;/p&gt;

&lt;p&gt;![Select Prometheus as your Data Source]&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%2Ft13quxk2jct3tqucy36r.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%2Ft13quxk2jct3tqucy36r.png" alt=" " width="800" height="353"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, input the &lt;strong&gt;Prometheus URL&lt;/strong&gt;, which, if you're running on localhost, should be &lt;code&gt;http://localhost:9090&lt;/code&gt; or &lt;code&gt;http://prometheus:9090&lt;/code&gt;.&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%2Ff25ieenkmgrg4lh6yyv4.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%2Ff25ieenkmgrg4lh6yyv4.png" alt=" " width="800" height="357"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once successful, you should see the success message at the bottom of the page.&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%2Fvuwue0bk9o5pbpxbbp62.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%2Fvuwue0bk9o5pbpxbbp62.png" alt=" " width="800" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All that’s left to do is create the dashboards to visualize the scraped data. To do that, navigate to the &lt;code&gt;/grafana-dashboards&lt;/code&gt; folder in &lt;code&gt;monitoring&lt;/code&gt; and copy the contents of the &lt;code&gt;node-exporter.json&lt;/code&gt; file in the repository. Once done, click on the &lt;strong&gt;“Dashboards”&lt;/strong&gt; option on the left-hand side of the page, and click on the &lt;strong&gt;“New”&lt;/strong&gt; button at the right-hand side of the screen. You will find three ways to create a dashboard — select the &lt;strong&gt;Import&lt;/strong&gt; method.&lt;/p&gt;

&lt;p&gt;![Choose the Import method to create the dashboard]&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%2Fw145d8g6z95ynkqfuszw.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%2Fw145d8g6z95ynkqfuszw.png" alt=" " width="800" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Proceed to paste the content of the &lt;code&gt;node-exporter.json&lt;/code&gt; into the &lt;strong&gt;"Import via dashboard JSON model"&lt;/strong&gt; section and click on the &lt;strong&gt;“Load”&lt;/strong&gt; button.&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%2Fourhbd2vymm79i2o3ttj.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%2Fourhbd2vymm79i2o3ttj.png" alt="Paste Copied Node Exporter Json" width="800" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the next page, select &lt;strong&gt;“Prometheus”&lt;/strong&gt; as the data source for your dashboard. Once done, you should be met with this dashboard displaying your &lt;strong&gt;host machine metrics&lt;/strong&gt;.&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%2Fjidloccp4xr4dgbz7ljw.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%2Fjidloccp4xr4dgbz7ljw.png" alt="Node Exporter Metrics" width="800" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From the image above, you can see metrics like “System Load” based on time averages, the “Root FS” usage, and a host of other insights.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;cadvisor&lt;/code&gt;, follow the same process to import the dashboard using the &lt;code&gt;cadvisor.json&lt;/code&gt;, and you should get something like this:&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%2Fan9x4ttw7sqsid2udem9.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%2Fan9x4ttw7sqsid2udem9.png" alt="Cadvisor Metrics" width="800" height="378"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The result above shows the &lt;strong&gt;CPU&lt;/strong&gt;, &lt;strong&gt;Memory&lt;/strong&gt;, &lt;strong&gt;Network&lt;/strong&gt;, and &lt;strong&gt;Misc&lt;/strong&gt; metrics of the different running containers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the Architecture
&lt;/h2&gt;

&lt;p&gt;Before deploying any application to the cloud, it's important to have a laid-out architecture or system design for how the application will be deployed. This matters because it helps you understand what works and what doesn't, along with the trade-offs between different architectural setups. Doing this allows you to optimize your deployment based on your specific use case and take into account cost, reliability, and scalability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Architectural Diagram
&lt;/h3&gt;

&lt;p&gt;Represented below is the application's architectural diagram to be deployed on AWS:&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%2Fxmk3qk128vbn6iazw54o.jpeg" 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%2Fxmk3qk128vbn6iazw54o.jpeg" alt="Project Architectural Diagram" width="800" height="410"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From the diagram above, you can see the different AWS services that come together to make the application production-ready. The diagram really just has two sets of services: &lt;strong&gt;network&lt;/strong&gt; and &lt;strong&gt;compute&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Under the &lt;strong&gt;network&lt;/strong&gt; layer, you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;VPC&lt;/strong&gt; – This isolates your application from the world.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Internet Gateway&lt;/strong&gt; – Attached to the VPC so it can communicate with the outside world.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Public Subnets&lt;/strong&gt; – Connected to the Internet Gateway and used to house public-facing resources like your Application Load Balancer (ALB), Public EC2 instance, and NAT Gateways.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NAT Gateways&lt;/strong&gt; – These allow resources in your private subnet to send traffic to the internet, while blocking incoming traffic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Private Subnets&lt;/strong&gt; – These house private services that don’t need to communicate directly with the outside world.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For your &lt;strong&gt;compute&lt;/strong&gt; resources, you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Application Load Balancer (ALB)&lt;/strong&gt; – Lives in the public subnet, provides an entry point for external users, and routes requests to the appropriate targets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bastion Host&lt;/strong&gt; – Also in the public subnet. This allows SSH access to the private EC2 instance and also doubles as the server running the Grafana container.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Private EC2 Instance&lt;/strong&gt; – Hosts both the frontend and backend containers, and also runs monitoring tools like Prometheus, Node Exporter, and cAdvisor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RDS (PostgreSQL)&lt;/strong&gt; – This is your managed database instance that holds all the application data.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Implementing the Architecture with Terraform
&lt;/h2&gt;

&lt;p&gt;Terraform is an Infrastructure as Code (IaC) tool, which is used for automatically provisioning resources in the cloud. Its need stems from a place of developers not wanting to "point and click" their way to the creation of resources, but rather have them automatically provisioned, while having a record of what changed and what did not for effective management.&lt;/p&gt;

&lt;p&gt;To provision your resources with Terraform, you begin by defining your &lt;code&gt;providers.tf&lt;/code&gt; file, where you specify the providers or "packages" terraform should install for your project. Here's the &lt;code&gt;providers.tf&lt;/code&gt; file for this project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;aws&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;syource&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/aws"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"6.5.0"&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;span class="k"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_region&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In your provider's Terraform file, you specify AWS as a required provider and assign the region from the variables Terraform file.&lt;/p&gt;

&lt;p&gt;The variables Terraform file is used to prevent repetition of variables across different files in your Terraform configuration.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;variables.tf&lt;/code&gt; file for this project, as seen below, you will specify a couple of variables that will be used throughout the project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"aws_region"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-2"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"tags"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
    &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
    &lt;span class="nx"&gt;developer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
    &lt;span class="nx"&gt;team&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"EphraimX"&lt;/span&gt;
    &lt;span class="nx"&gt;developer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"TheJackalX"&lt;/span&gt;
    &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Production"&lt;/span&gt;
    &lt;span class="nx"&gt;team&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Boogey Team"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"DB_PORT"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;
  &lt;span class="nx"&gt;sensitive&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"DB_NAME"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"DB_USER"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;sensitive&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"DB_PASSWORD"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;sensitive&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"DB_IDENTIFIER"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"roi-calculator"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"DB_INSTANCE_CLASS"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"db.t3.micro"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"DB_ENGINE"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"postgres"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"DB_TYPE"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"postgresql"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="c1"&gt;# variable "NEXT_PUBLIC_APIURL" {&lt;/span&gt;
&lt;span class="c1"&gt;#   type = string&lt;/span&gt;
&lt;span class="c1"&gt;#   sensitive = true&lt;/span&gt;
&lt;span class="c1"&gt;# }&lt;/span&gt;


&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"CLIENT_URL"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"*"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Specified in the &lt;code&gt;variables.tf&lt;/code&gt; are different variables such as the AWS region you'll be deploying your resources to, the tags for your resources, the database port, and other database values such as the name, the user, the password, instance identifier, instance class, database engine, and the database type. Also included is the client URL, which is added to the list of allowed origins in the backend code to prevent a CORS error,r as seen in the snippet below:&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="c1"&gt;# CORS Settings
&lt;/span&gt;&lt;span class="n"&gt;CLIENT_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CLIENT_URL&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;http://localhost:3000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ALLOyouD_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;http://localhost&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;http://localhost:3000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# or the port your frontend uses
&lt;/span&gt;    &lt;span class="n"&gt;CLIENT_URL&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this in place, you can go on to build your &lt;code&gt;main.tf&lt;/code&gt;, the file that will handle all your provisioning and the configurations.&lt;/p&gt;

&lt;p&gt;Starting with the VPC and Subnets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;#################################################&lt;/span&gt;
&lt;span class="c1"&gt;## AWS VPC and Subnets&lt;/span&gt;
&lt;span class="c1"&gt;#################################################&lt;/span&gt;

&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_vpc"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_vpc"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_block&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.10.0.0/16"&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_subnet"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_public_subnet_one"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_block&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.10.10.0/24"&lt;/span&gt;
  &lt;span class="nx"&gt;availability_zone&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-2a"&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_subnet"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_public_subnet_two"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_block&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.10.20.0/24"&lt;/span&gt;
  &lt;span class="nx"&gt;availability_zone&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-2b"&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_subnet"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_private_subnet_one"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_block&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.10.30.0/24"&lt;/span&gt;
  &lt;span class="nx"&gt;availability_zone&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-2a"&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_subnet"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_private_subnet_two"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_block&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.10.40.0/24"&lt;/span&gt;
  &lt;span class="nx"&gt;availability_zone&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-2b"&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, you define your VPC and the CIDR block you want it to operate on. CIDR blocks are how you define IP address ranges in your VPC; they tell AWS which IPs your network will own. Instead of assigning individual IPs, you just define a range like 10.10.0.0/16, and AWS handles the rest. With this in place, you define your subnets, both public and private, deploying them in different availability zones in order to ensure high availability.&lt;/p&gt;

&lt;p&gt;Next, you define your internet gateway and route tables for your public subnets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;#################################################&lt;/span&gt;
&lt;span class="c1"&gt;## Internet Gateway and Route Tables - Public &lt;/span&gt;
&lt;span class="c1"&gt;#################################################&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_internet_gateway"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_igw"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route_table"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_route_table"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;route&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cidr_block&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;
    &lt;span class="nx"&gt;gateway_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_internet_gateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_igw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route_table_association"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_route_table_association_public_subnet_one"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;route_table_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_route_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_route_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_public_subnet_one&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route_table_association"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_route_table_association_public_subnet_two"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;route_table_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_route_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_route_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_public_subnet_two&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An internet gateway is important when working in a VPC as it allows resources within it to access the internet. Without it, resources in your VPC will be completely shut off from the internet. You also define a route table here, which routes all incoming and outgoing traffic from the internet through your internet gateway. And lastly, for your public subnets to be truly public, you have to associate them with the route table connected to the internet gateway.&lt;/p&gt;

&lt;p&gt;Next, you define your Elastic IP address, NAT gateway, and corresponding route tables for your private subnets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;#############################################################&lt;/span&gt;
&lt;span class="c1"&gt;## EIP, NAT Gateway, Route Tables - Private Subnet One&lt;/span&gt;
&lt;span class="c1"&gt;#########################################################&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_eip"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_ngw_eip_private_subnet_one"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;domain&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"vpc"&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_nat_gateway"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_ngw_private_subnet_one"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_public_subnet_one&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;allocation_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_eip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_ngw_eip_private_subnet_one&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;
  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;aws_internet_gateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_igw&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route_table"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_route_table_private_subnet_one"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;route&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cidr_block&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;
    &lt;span class="nx"&gt;nat_gateway_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_nat_gateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_ngw_private_subnet_one&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route_table_association"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_route_table_association_private_subnet_one"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_private_subnet_one&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;route_table_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_route_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_route_table_private_subnet_one&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="c1"&gt;#############################################################&lt;/span&gt;
&lt;span class="c1"&gt;## EIP, NAT Gateway, Route Tables - Private Subnet Two&lt;/span&gt;
&lt;span class="c1"&gt;#########################################################&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_eip"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_ngw_eip_private_subnet_two"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;domain&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"vpc"&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_nat_gateway"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_ngw_private_subnet_two"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_public_subnet_two&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;allocation_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_eip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_ngw_eip_private_subnet_two&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;
  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;aws_internet_gateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_igw&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route_table"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_route_table_private_subnet_two"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;route&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cidr_block&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;
    &lt;span class="nx"&gt;nat_gateway_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_nat_gateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_ngw_private_subnet_two&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route_table_association"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_route_table_association_private_subnet_two"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_private_subnet_two&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;route_table_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_route_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_route_table_private_subnet_two&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is generally expected that resources in your private subnet not have any form of communication to the internet, as it hosts very sensitive applications. But what happens in production is that some of these resources need to access the internet for things like patches and updates. &lt;/p&gt;

&lt;p&gt;To solve this, a NAT gateway is placed in a public subnet and attached to the private subnet through a route table. And for the NAT gateway to be able to communicate with the internet, an Elastic IP address (EIP) is attached to provide it with a static public IPv4 address for this purpose.&lt;/p&gt;

&lt;p&gt;All these define your network resources for the project. For the compute resources, the first set of resources to define is the security groups for the EC2 servers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;#################################################&lt;/span&gt;
&lt;span class="c1"&gt;## Bastion Host Security Group&lt;/span&gt;
&lt;span class="c1"&gt;#################################################&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_security_group"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_bastion_host_sg"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"roi-calculator-bastion-host-sg"&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_vpc_security_group_ingress_rule"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_grafana_sg_ingress"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;security_group_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_bastion_host_sg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"grafana"&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_ipv4&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;
  &lt;span class="nx"&gt;from_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;
  &lt;span class="nx"&gt;to_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;
  &lt;span class="nx"&gt;ip_protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_vpc_security_group_ingress_rule"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_bastion_ssh_ingress"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;security_group_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_bastion_host_sg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ssh"&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_ipv4&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;   &lt;span class="c1"&gt;# Open to the world – for production, restrict this!&lt;/span&gt;
  &lt;span class="nx"&gt;from_port&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;
  &lt;span class="nx"&gt;to_port&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;
  &lt;span class="nx"&gt;ip_protocol&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_vpc_security_group_egress_rule"&lt;/span&gt; &lt;span class="s2"&gt;"bastion_host_allow_all_traffic_ipv4"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;security_group_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_bastion_host_sg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_ipv4&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;
  &lt;span class="nx"&gt;ip_protocol&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"-1"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="c1"&gt;#################################################&lt;/span&gt;
&lt;span class="c1"&gt;## Production Host Security Group&lt;/span&gt;
&lt;span class="c1"&gt;#################################################&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_security_group"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_production_host_sg"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"roi-calculator-production-host-sg"&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_vpc_security_group_ingress_rule"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_production_ssh_ingress"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;security_group_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_production_host_sg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ssh"&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_ipv4&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cidr_block&lt;/span&gt;   &lt;span class="c1"&gt;# Open to the world – for production, restrict this!&lt;/span&gt;
  &lt;span class="nx"&gt;from_port&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;
  &lt;span class="nx"&gt;to_port&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;
  &lt;span class="nx"&gt;ip_protocol&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_vpc_security_group_ingress_rule"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_http_sg_ingress"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;security_group_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_production_host_sg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http-and-also-for-next-js"&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_ipv4&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cidr_block&lt;/span&gt;
  &lt;span class="nx"&gt;from_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
  &lt;span class="nx"&gt;to_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
  &lt;span class="nx"&gt;ip_protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_vpc_security_group_ingress_rule"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_https_sg_ingress"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;security_group_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_production_host_sg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https"&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_ipv4&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cidr_block&lt;/span&gt;
  &lt;span class="nx"&gt;from_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt;
  &lt;span class="nx"&gt;to_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt;
  &lt;span class="nx"&gt;ip_protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_vpc_security_group_ingress_rule"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_fastapi_sg_ingress"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;security_group_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_production_host_sg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"fastapi"&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_ipv4&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cidr_block&lt;/span&gt;
  &lt;span class="nx"&gt;from_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8000&lt;/span&gt;
  &lt;span class="nx"&gt;to_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8000&lt;/span&gt;
  &lt;span class="nx"&gt;ip_protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_vpc_security_group_ingress_rule"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_next_js_sg_ingress"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;security_group_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_production_host_sg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"next_js"&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_ipv4&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cidr_block&lt;/span&gt;
  &lt;span class="nx"&gt;from_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8081&lt;/span&gt;
  &lt;span class="nx"&gt;to_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8081&lt;/span&gt;
  &lt;span class="nx"&gt;ip_protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_vpc_security_group_ingress_rule"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_prometheus_sg_ingress"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;security_group_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_production_host_sg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"prometheus"&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_ipv4&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cidr_block&lt;/span&gt;
  &lt;span class="nx"&gt;from_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;9090&lt;/span&gt;
  &lt;span class="nx"&gt;to_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;9090&lt;/span&gt;
  &lt;span class="nx"&gt;ip_protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_vpc_security_group_ingress_rule"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_cadvisor_sg_ingress"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;security_group_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_production_host_sg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"cadvisor"&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_ipv4&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cidr_block&lt;/span&gt;
  &lt;span class="nx"&gt;from_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8085&lt;/span&gt;
  &lt;span class="nx"&gt;to_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8085&lt;/span&gt;
  &lt;span class="nx"&gt;ip_protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_vpc_security_group_ingress_rule"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_node_exporter_sg_ingress"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;security_group_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_production_host_sg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"node_exporter"&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_ipv4&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cidr_block&lt;/span&gt;
  &lt;span class="nx"&gt;from_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;9100&lt;/span&gt;
  &lt;span class="nx"&gt;to_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;9100&lt;/span&gt;
  &lt;span class="nx"&gt;ip_protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_vpc_security_group_egress_rule"&lt;/span&gt; &lt;span class="s2"&gt;"production_host_allow_all_traffic_ipv4"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;security_group_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_production_host_sg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_ipv4&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;
  &lt;span class="nx"&gt;ip_protocol&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"-1"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="c1"&gt;#################################################&lt;/span&gt;
&lt;span class="c1"&gt;## Application Load Balancer Security Group&lt;/span&gt;
&lt;span class="c1"&gt;#################################################&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_security_group"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_alb_sg"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"roi-calculator-alb-sg"&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_vpc_security_group_ingress_rule"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_alb_sg_http"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;security_group_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_alb_sg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"alb_http"&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_ipv4&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;
  &lt;span class="nx"&gt;from_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
  &lt;span class="nx"&gt;to_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
  &lt;span class="nx"&gt;ip_protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_vpc_security_group_ingress_rule"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_alb_sg_https"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;security_group_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_alb_sg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"alb_https"&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_ipv4&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;
  &lt;span class="nx"&gt;from_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt;
  &lt;span class="nx"&gt;to_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt;
  &lt;span class="nx"&gt;ip_protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_vpc_security_group_egress_rule"&lt;/span&gt; &lt;span class="s2"&gt;"alb_allow_all_traffic_ipv4"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;security_group_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_alb_sg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_ipv4&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;
  &lt;span class="nx"&gt;ip_protocol&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"-1"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A VPC security group is responsible for determining what traffic comes in and out, from what source, and also what port the traffic should be directed to. For the bastion host in this project, you defined two ingress rules, ingress means what traffic should be allowed into the instance. &lt;/p&gt;

&lt;p&gt;These rules allow traffic from any IP address to port 3000 for accessing Grafana, and port 22 for accessing the system via SSH. In the example above, access is granted from all IP addresses, but in production, limit SSH access to just your IP address. Lastly, on the bastion host, you allow traffic out of the instance to all IP addresses.&lt;/p&gt;

&lt;p&gt;For the production host, you have a similar setup, just with more ports. For ingress rules, you have 22 for SSH, 80 &amp;amp; 8081 for the frontend application, depending on the configuration, 8000 for the backend application, 443 for HTTPS, 9090 for Prometheus, 8085 for cAdvisor, 9100 for the Node Exporter, and lastly, for the egress rules, you allow traffic to all IP addresses.&lt;/p&gt;

&lt;p&gt;Next, you define the EC2 instance for both the bastion host and production host.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;#################################################&lt;/span&gt;
&lt;span class="c1"&gt;## Bastion Host &lt;/span&gt;
&lt;span class="c1"&gt;#################################################&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_bastion_host_ec2_public_subnet_one"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ami&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_ami&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ubuntu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"t3.xlarge"&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_security_group_ids&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_bastion_host_sg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_public_subnet_one&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;key_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"rayda-application"&lt;/span&gt;
  &lt;span class="nx"&gt;associate_public_ip_address&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;user_data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"scripts/bastion-host.sh"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_bastion_host_ec2_public_subnet_two"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ami&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_ami&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ubuntu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"t3.xlarge"&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_security_group_ids&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_bastion_host_sg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_public_subnet_two&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;key_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"rayda-application"&lt;/span&gt;
  &lt;span class="nx"&gt;associate_public_ip_address&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;user_data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"scripts/bastion-host.sh"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="c1"&gt;#################################################&lt;/span&gt;
&lt;span class="c1"&gt;## Production Host&lt;/span&gt;
&lt;span class="c1"&gt;#################################################&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_production_host_ec2_private_subnet_one"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ami&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_ami&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ubuntu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"t3.xlarge"&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_security_group_ids&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_production_host_sg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_private_subnet_one&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;key_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"rayda-application"&lt;/span&gt;
  &lt;span class="nx"&gt;associate_public_ip_address&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="nx"&gt;user_data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;templatefile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;module}&lt;/span&gt;&lt;span class="s2"&gt;/scripts/production-host.sh"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;db_host&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_db_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;
    &lt;span class="nx"&gt;db_port&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB_PORT&lt;/span&gt;
    &lt;span class="nx"&gt;db_name&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB_NAME&lt;/span&gt;
    &lt;span class="nx"&gt;db_user&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB_USER&lt;/span&gt;
    &lt;span class="nx"&gt;db_password&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB_PASSWORD&lt;/span&gt;
    &lt;span class="nx"&gt;db_type&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB_TYPE&lt;/span&gt;
    &lt;span class="nx"&gt;client_url&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CLIENT_URL&lt;/span&gt;
    &lt;span class="nx"&gt;MY_IP&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"127.0.0.1"&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_production_host_ec2_private_subnet_two"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ami&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_ami&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ubuntu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"t3.xlarge"&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_security_group_ids&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_production_host_sg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_private_subnet_two&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;key_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"rayda-application"&lt;/span&gt;
  &lt;span class="nx"&gt;associate_public_ip_address&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="nx"&gt;user_data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;templatefile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;module}&lt;/span&gt;&lt;span class="s2"&gt;/scripts/production-host.sh"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;db_host&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_db_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;
    &lt;span class="nx"&gt;db_port&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB_PORT&lt;/span&gt;
    &lt;span class="nx"&gt;db_name&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB_NAME&lt;/span&gt;
    &lt;span class="nx"&gt;db_user&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB_USER&lt;/span&gt;
    &lt;span class="nx"&gt;db_password&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB_PASSWORD&lt;/span&gt;
    &lt;span class="nx"&gt;db_type&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB_TYPE&lt;/span&gt;
    &lt;span class="nx"&gt;client_url&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CLIENT_URL&lt;/span&gt;
    &lt;span class="nx"&gt;MY_IP&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"127.0.0.1"&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For both these hosts, you define the properties of the EC2 instances, such as the instance class, which is set to "t3.xlarge", and the VPC security groups are set to the security group you defined initially. You create two instances and place them in two different subnets in different availability zones. You set the key name for SSH access, and you associate a public IP in case of a bastion host, but not for the production host. There's also the AMI or Amazon Machine Image, which provides the required software to set up and boot an Amazon EC2 instance. For the AMI, you get the ID of the image you want by retrieving the image properties from AWS in &lt;code&gt;data.tf&lt;/code&gt; as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_ami"&lt;/span&gt; &lt;span class="s2"&gt;"ubuntu"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;most_recent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="nx"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"name"&lt;/span&gt;
    &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"virtualization-type"&lt;/span&gt;
    &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"hvm"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;owners&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"099720109477"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# Canonical&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As seen in the config above, you tell AWS to get you the most recent image running Ubuntu Jammy 22.04. You select virtualization to Hardware Virtual Machine (HVM) and select the owners to be canonical.&lt;/p&gt;

&lt;p&gt;Another important configuration of both the production and bastion host is the &lt;code&gt;user_data&lt;/code&gt;. The &lt;code&gt;user_data&lt;/code&gt; is are set of commands you give to AWS to run at boot time. For an EC2 instance, this allows you to predefine installation steps without having to access these servers and run the installation manually.&lt;/p&gt;

&lt;p&gt;For the bastion host, the &lt;code&gt;user_data&lt;/code&gt; is using the file function to load the &lt;code&gt;bastion-host.sh&lt;/code&gt; file in scripts. Here is the contents of the bastion scripts file:&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="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-x&lt;/span&gt;


&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; unzip curl

curl &lt;span class="s2"&gt;"https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"awscliv2.zip"&lt;/span&gt;
unzip awscliv2.zip
&lt;span class="nb"&gt;sudo&lt;/span&gt; ./aws/install

&lt;span class="c"&gt;# Update and install Docker prerequisites&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; apt-transport-https ca-certificates curl software-properties-common git

&lt;span class="c"&gt;# Add Docker GPG key and repo&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://download.docker.com/linux/ubuntu/gpg | &lt;span class="nb"&gt;sudo &lt;/span&gt;apt-key add -
&lt;span class="nb"&gt;sudo &lt;/span&gt;add-apt-repository &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="s2"&gt;"deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable"&lt;/span&gt;

&lt;span class="c"&gt;# Install Docker&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; docker-ce

&lt;span class="c"&gt;# Verify Docker is running&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl is-active &lt;span class="nt"&gt;--quiet&lt;/span&gt; docker &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Docker is running"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Docker is not running"&lt;/span&gt;

&lt;span class="c"&gt;# Allow current user to run Docker without sudo&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; docker ubuntu

&lt;span class="c"&gt;# Clone your repo&lt;/span&gt;
git clone https://github.com/EphraimX/roi-calculator.git

&lt;span class="c"&gt;# Change directory&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;roi-calculator/monitoring

&lt;span class="c"&gt;# Build and start the Grafana service from your Docker Compose file&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; monitoring-docker-compose.yml up &lt;span class="nt"&gt;-d&lt;/span&gt; grafana
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the &lt;code&gt;user_data&lt;/code&gt; for the bastion host, you set your initial parameters, such as &lt;code&gt;set -e&lt;/code&gt; for exit on failure and &lt;code&gt;set -x&lt;/code&gt; to log your process as it runs. Next, you update your packages and install much-needed packages such as the AWS CLI and Docker. Then you verify Docker is running and allow the current user to run Docker without sudo. Lastly, you clone the repository, navigate to the monitoring folder, and start the Grafana service.&lt;/p&gt;

&lt;p&gt;The process is also very similar to the &lt;code&gt;user_data&lt;/code&gt; of the production host, as seen below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nx"&gt;set&lt;/span&gt; &lt;span class="nx"&gt;-e&lt;/span&gt;
&lt;span class="nx"&gt;set&lt;/span&gt; &lt;span class="nx"&gt;-x&lt;/span&gt;

&lt;span class="nx"&gt;sudo&lt;/span&gt; &lt;span class="nx"&gt;apt&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt;
&lt;span class="nx"&gt;sudo&lt;/span&gt; &lt;span class="nx"&gt;apt&lt;/span&gt; &lt;span class="nx"&gt;install&lt;/span&gt; &lt;span class="nx"&gt;-y&lt;/span&gt; &lt;span class="nx"&gt;unzip&lt;/span&gt; &lt;span class="nx"&gt;curl&lt;/span&gt;

&lt;span class="nx"&gt;curl&lt;/span&gt; &lt;span class="s2"&gt;"https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip"&lt;/span&gt; &lt;span class="nx"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"awscliv2.zip"&lt;/span&gt;
&lt;span class="nx"&gt;unzip&lt;/span&gt; &lt;span class="nx"&gt;awscliv2&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zip&lt;/span&gt;
&lt;span class="nx"&gt;sudo&lt;/span&gt; &lt;span class="err"&gt;./&lt;/span&gt;&lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;

&lt;span class="c1"&gt;# Step 1: Request a metadata session token (valid for 6 hours)&lt;/span&gt;
&lt;span class="nx"&gt;TOKEN&lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;curl&lt;/span&gt; &lt;span class="nx"&gt;-X&lt;/span&gt; &lt;span class="nx"&gt;PUT&lt;/span&gt; &lt;span class="s2"&gt;"http://169.254.169.254/latest/api/token"&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;
  &lt;span class="nx"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-aws-ec2-metadata-token-ttl-seconds: 21600"&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Step 2: Use that token to access instance metadata securely&lt;/span&gt;
&lt;span class="nx"&gt;MY_IP&lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;curl&lt;/span&gt; &lt;span class="nx"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-aws-ec2-metadata-token: &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;TOKEN"&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;
  &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//169.254.169.254/latest/meta-data/local-ipv4)&lt;/span&gt;

&lt;span class="c1"&gt;# Set environment variables&lt;/span&gt;
&lt;span class="c1"&gt;# export NEXT_PUBLIC_APIURL="__next_public_apiurl__"&lt;/span&gt;
&lt;span class="nx"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;NEXT_PUBLIC_APIURL&lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"http://&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;(curl -H "&lt;/span&gt;&lt;span class="nx"&gt;X-aws-ec2-metadata-token&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;curl&lt;/span&gt; &lt;span class="nx"&gt;-X&lt;/span&gt; &lt;span class="nx"&gt;PUT&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//169.254.169.254/latest/api/token \&lt;/span&gt;
                                                                              &lt;span class="nx"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-aws-ec2-metadata-token-ttl-seconds: 21600"&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;" &lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
                                                                              http://169.254.169.254/latest/meta-data/local-ipv4):8000/api"&lt;/span&gt;
&lt;span class="nx"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;DB_HOST&lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;db_host&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nx"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;DB_PORT&lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;db_port&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nx"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;DB_NAME&lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;db_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nx"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;DB_USER&lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;db_user&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nx"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;DB_PASSWORD&lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;db_password&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nx"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;DB_TYPE&lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;db_type&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nx"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;CLIENT_URL&lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;client_url&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# echo "NEXT_PUBLIC_APIURL=http://${MY_IP}:8000/api" &amp;gt;&amp;gt; /etc/environment&lt;/span&gt;
&lt;span class="nx"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"DB_HOST=&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;DB_HOST"&lt;/span&gt;
&lt;span class="nx"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"DB_PORT=&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;DB_PORT"&lt;/span&gt;
&lt;span class="nx"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"DB_NAME=&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;DB_NAME"&lt;/span&gt;
&lt;span class="nx"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"DB_USER=&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;DB_USER"&lt;/span&gt;
&lt;span class="nx"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"DB_PASSWORD=&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;DB_PASSWORD"&lt;/span&gt;
&lt;span class="nx"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"DB_TYPE=&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;DB_TYPE"&lt;/span&gt;
&lt;span class="nx"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"CLIENT_URL=&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;CLIENT_URL"&lt;/span&gt;

&lt;span class="c1"&gt;# Install Docker&lt;/span&gt;
&lt;span class="nx"&gt;sudo&lt;/span&gt; &lt;span class="nx"&gt;apt&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt; &lt;span class="nx"&gt;-y&lt;/span&gt;
&lt;span class="nx"&gt;sudo&lt;/span&gt; &lt;span class="nx"&gt;apt&lt;/span&gt; &lt;span class="nx"&gt;install&lt;/span&gt; &lt;span class="nx"&gt;-y&lt;/span&gt; &lt;span class="nx"&gt;apt-transport-https&lt;/span&gt; &lt;span class="nx"&gt;ca-certificates&lt;/span&gt; &lt;span class="nx"&gt;curl&lt;/span&gt; &lt;span class="nx"&gt;software-properties-common&lt;/span&gt; &lt;span class="nx"&gt;git&lt;/span&gt;
&lt;span class="nx"&gt;curl&lt;/span&gt; &lt;span class="nx"&gt;-fsSL&lt;/span&gt; &lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//download.docker.com/linux/ubuntu/gpg | sudo apt-key add -&lt;/span&gt;
&lt;span class="nx"&gt;sudo&lt;/span&gt; &lt;span class="nx"&gt;add-apt-repository&lt;/span&gt; &lt;span class="nx"&gt;-y&lt;/span&gt; &lt;span class="s2"&gt;"deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable"&lt;/span&gt;
&lt;span class="nx"&gt;sudo&lt;/span&gt; &lt;span class="nx"&gt;apt&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt; &lt;span class="nx"&gt;-y&lt;/span&gt;
&lt;span class="nx"&gt;sudo&lt;/span&gt; &lt;span class="nx"&gt;apt&lt;/span&gt; &lt;span class="nx"&gt;install&lt;/span&gt; &lt;span class="nx"&gt;-y&lt;/span&gt; &lt;span class="nx"&gt;docker-ce&lt;/span&gt;
&lt;span class="nx"&gt;sudo&lt;/span&gt; &lt;span class="nx"&gt;systemctl&lt;/span&gt; &lt;span class="nx"&gt;is-active&lt;/span&gt; &lt;span class="nx"&gt;--quiet&lt;/span&gt; &lt;span class="nx"&gt;docker&lt;/span&gt; &lt;span class="err"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Docker is running"&lt;/span&gt; &lt;span class="err"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Docker is not running"&lt;/span&gt;
&lt;span class="nx"&gt;sudo&lt;/span&gt; &lt;span class="nx"&gt;usermod&lt;/span&gt; &lt;span class="nx"&gt;-aG&lt;/span&gt; &lt;span class="nx"&gt;docker&lt;/span&gt; &lt;span class="nx"&gt;ubuntu&lt;/span&gt;

&lt;span class="c1"&gt;# Clone repo&lt;/span&gt;
&lt;span class="nx"&gt;git&lt;/span&gt; &lt;span class="nx"&gt;clone&lt;/span&gt; &lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//github.com/EphraimX/roi-calculator.git&lt;/span&gt;
&lt;span class="nx"&gt;cd&lt;/span&gt; &lt;span class="nx"&gt;roi-calculator&lt;/span&gt;


&lt;span class="c1"&gt;# Create Docker Network&lt;/span&gt;
&lt;span class="nx"&gt;sudo&lt;/span&gt; &lt;span class="nx"&gt;docker&lt;/span&gt; &lt;span class="nx"&gt;network&lt;/span&gt; &lt;span class="nx"&gt;create&lt;/span&gt; &lt;span class="nx"&gt;roi-calculator-network&lt;/span&gt;

&lt;span class="c1"&gt;# Frontend&lt;/span&gt;
&lt;span class="nx"&gt;cd&lt;/span&gt; &lt;span class="nx"&gt;client-side&lt;/span&gt;
&lt;span class="nx"&gt;sudo&lt;/span&gt; &lt;span class="nx"&gt;docker&lt;/span&gt; &lt;span class="nx"&gt;build&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;
  &lt;span class="nx"&gt;-f&lt;/span&gt; &lt;span class="nx"&gt;Dockerfile&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;
  &lt;span class="nx"&gt;--build-arg&lt;/span&gt; &lt;span class="nx"&gt;NEXT_PUBLIC_APIURL&lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;$NEXT_PUBLIC_APIURL&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;
  &lt;span class="nx"&gt;-t&lt;/span&gt; &lt;span class="nx"&gt;roi-calculator-frontend&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;
&lt;span class="nx"&gt;sudo&lt;/span&gt; &lt;span class="nx"&gt;docker&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="nx"&gt;-d&lt;/span&gt; &lt;span class="nx"&gt;--name&lt;/span&gt; &lt;span class="nx"&gt;roi-calculator-frontend&lt;/span&gt; &lt;span class="nx"&gt;--network&lt;/span&gt; &lt;span class="nx"&gt;roi-calculator-network&lt;/span&gt; &lt;span class="nx"&gt;-p&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt; &lt;span class="nx"&gt;roi-calculator-frontend&lt;/span&gt;

&lt;span class="c1"&gt;# Backend&lt;/span&gt;
&lt;span class="nx"&gt;cd&lt;/span&gt; &lt;span class="err"&gt;../&lt;/span&gt;&lt;span class="nx"&gt;server-side&lt;/span&gt;
&lt;span class="nx"&gt;sudo&lt;/span&gt; &lt;span class="nx"&gt;docker&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="nx"&gt;-d&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;
  &lt;span class="nx"&gt;--name&lt;/span&gt; &lt;span class="nx"&gt;roi-calculator-backend&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;
  &lt;span class="nx"&gt;--network&lt;/span&gt; &lt;span class="nx"&gt;roi-calculator-network&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;
  &lt;span class="nx"&gt;-e&lt;/span&gt; &lt;span class="nx"&gt;DB_HOST&lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;$DB_HOST&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;
  &lt;span class="nx"&gt;-e&lt;/span&gt; &lt;span class="nx"&gt;DB_PORT&lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;$DB_PORT&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;
  &lt;span class="nx"&gt;-e&lt;/span&gt; &lt;span class="nx"&gt;DB_NAME&lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;$DB_NAME&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;
  &lt;span class="nx"&gt;-e&lt;/span&gt; &lt;span class="nx"&gt;DB_USER&lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;$DB_USER&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;
  &lt;span class="nx"&gt;-e&lt;/span&gt; &lt;span class="nx"&gt;DB_PASSWORD&lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;$DB_PASSWORD&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;
  &lt;span class="nx"&gt;-e&lt;/span&gt; &lt;span class="nx"&gt;DB_TYPE&lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;$DB_TYPE&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;
  &lt;span class="nx"&gt;-e&lt;/span&gt; &lt;span class="nx"&gt;CLIENT_URL&lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;$CLIENT_URL&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;
  &lt;span class="nx"&gt;-p&lt;/span&gt; &lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8000&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;
  &lt;span class="nx"&gt;ephraimx57&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;roi-calculator-backend&lt;/span&gt;

&lt;span class="c1"&gt;# Monitoring&lt;/span&gt;
&lt;span class="nx"&gt;cd&lt;/span&gt; &lt;span class="err"&gt;../&lt;/span&gt;&lt;span class="nx"&gt;monitoring&lt;/span&gt;
&lt;span class="nx"&gt;sudo&lt;/span&gt; &lt;span class="nx"&gt;docker&lt;/span&gt; &lt;span class="nx"&gt;compose&lt;/span&gt; &lt;span class="nx"&gt;-f&lt;/span&gt; &lt;span class="nx"&gt;monitoring-docker-compose&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;yml&lt;/span&gt; &lt;span class="nx"&gt;up&lt;/span&gt; &lt;span class="nx"&gt;-d&lt;/span&gt; &lt;span class="nx"&gt;prometheus&lt;/span&gt; &lt;span class="nx"&gt;cadvisor&lt;/span&gt; &lt;span class="nx"&gt;node_exporter&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, you perform the initial process involved for the &lt;code&gt;bastion-host.sh&lt;/code&gt;. Past there, you generate the NEXT_PUBLIC_APIURL, which is the backend API to communicate with the frontend. Then you export the environment variables, perform a couple of installations including Docker, and then launch the frontend and backend of the. &lt;/p&gt;

&lt;p&gt;To launch the frontend application, you pull the repository, navigate to the "client-side" directory, and build and run the Dockerfile with the appropriate parameters. For the backend, you run the already published image at "ephraimx57/roi-calcylator-backend" with the environment variables that you're initially passed to the script. Lastly, navigate to the monitoring directory to start Prometheus, cAdvisor, and Node Exporter.&lt;/p&gt;

&lt;p&gt;After the production host, the next resource for you to implement is the RDS Postgres database, which is displayed below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;#################################################&lt;/span&gt;
&lt;span class="c1"&gt;## AWS RDS&lt;/span&gt;
&lt;span class="c1"&gt;#################################################&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_db_subnet_group"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_rds_db_subnet_group"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"roi-calculator-rds-db-subnet-group"&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_ids&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_private_subnet_one&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_private_subnet_two&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_security_group"&lt;/span&gt; &lt;span class="s2"&gt;"rds_sg"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"rds-sg"&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow Postgres inbound traffic"&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;ingress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow Postgres from my IP"&lt;/span&gt;
    &lt;span class="nx"&gt;from_port&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5432&lt;/span&gt;
    &lt;span class="nx"&gt;to_port&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5432&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
    &lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_private_subnet_one&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cidr_block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_private_subnet_two&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cidr_block&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;egress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;from_port&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="nx"&gt;to_port&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"-1"&lt;/span&gt;
    &lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_private_subnet_one&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cidr_block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_private_subnet_two&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cidr_block&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;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_db_instance"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;identifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB_IDENTIFIER&lt;/span&gt;
  &lt;span class="nx"&gt;allocated_storage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
  &lt;span class="nx"&gt;db_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB_NAME&lt;/span&gt;
  &lt;span class="nx"&gt;engine&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB_ENGINE&lt;/span&gt;
  &lt;span class="nx"&gt;engine_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"17.5"&lt;/span&gt;
  &lt;span class="nx"&gt;instance_class&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB_INSTANCE_CLASS&lt;/span&gt;
  &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB_USER&lt;/span&gt;
  &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB_PASSWORD&lt;/span&gt;
  &lt;span class="nx"&gt;skip_final_snapshot&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB_PORT&lt;/span&gt;
  &lt;span class="nx"&gt;publicly_accessible&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="nx"&gt;db_subnet_group_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_db_subnet_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_rds_db_subnet_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_security_group_ids&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rds_sg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;p&gt;The RDS instance is pretty straightforward to set up. You define a "db_subnet_group", which allows for the high availability of the database. Next, you have the security group, which allows the database to be reached on port 5432 from resources within the VPC. And finally, you create the instance itself with some parameters already specified in your &lt;code&gt;variables.tf&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Your last resource to create is the Application Load Balancer (ALB).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;#################################################&lt;/span&gt;
&lt;span class="c1"&gt;## Application Load Balancer&lt;/span&gt;
&lt;span class="c1"&gt;#################################################&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lb"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_aws_lb"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"roi-calculator-aws-lb"&lt;/span&gt;
  &lt;span class="nx"&gt;internal&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="nx"&gt;load_balancer_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"application"&lt;/span&gt;
  &lt;span class="nx"&gt;security_groups&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_alb_sg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;subnets&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_public_subnet_one&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_public_subnet_two&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lb_target_group"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_aws_lb_target_group"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"roi-calc-aws-lb-tg-prisub-one"&lt;/span&gt;
  &lt;span class="nx"&gt;port&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
  &lt;span class="nx"&gt;protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HTTP"&lt;/span&gt;
  &lt;span class="nx"&gt;target_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"instance"&lt;/span&gt;
  &lt;span class="nx"&gt;health_check&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;path&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HTTP"&lt;/span&gt;
    &lt;span class="nx"&gt;healthy_threshold&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="nx"&gt;unhealthy_threshold&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="nx"&gt;timeout&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
    &lt;span class="nx"&gt;interval&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lb_listener"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_alb_sg_listener_private_subnet_one"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="nx"&gt;load_balancer_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_lb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_aws_lb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="nx"&gt;port&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"80"&lt;/span&gt;
  &lt;span class="nx"&gt;protocol&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HTTP"&lt;/span&gt;

  &lt;span class="nx"&gt;default_action&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;type&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"forward"&lt;/span&gt;
    &lt;span class="nx"&gt;target_group_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_lb_target_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_aws_lb_target_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lb_target_group_attachment"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_aws_lb_target_group_attachment_private_subnet_one"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;target_group_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_lb_target_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_aws_lb_target_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="nx"&gt;target_id&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_production_host_ec2_private_subnet_two&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;port&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;resyource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lb_target_group_attachment"&lt;/span&gt; &lt;span class="s2"&gt;"roi_calculator_aws_lb_target_group_attachment_private_subnet_two"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;target_group_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_lb_target_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_aws_lb_target_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="nx"&gt;target_id&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roi_calculator_production_host_ec2_private_subnet_two&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;port&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main purpose of the ALB is to balance load across different prospective loads. For this application, the ALB is also preferred because it provides a URL to easily access internal-facing applications. To define your ALB, you need to specify the target group. A target group describes the nature of the target machine network. You will also require a listener to listen to all the requests made and forward them to the right target group. Finally, you attach the target group to the private subnet, which enables it to reach the resources hidden behind this private subnet.&lt;/p&gt;

&lt;p&gt;##CI/CD With GitHub Actions&lt;/p&gt;

&lt;p&gt;Once your Terraform infrastructure is done, you are now ready to automate the deployment using GitHub Actions. GitHub Actions is a Continuous Integration and Continuous Deployment tool that is used by companies to automate the testing, integration, and deployment of their code. It helps streamline development and prevents the deployment of code that does not pass certain criteria or tests.&lt;/p&gt;

&lt;p&gt;Using CI/CD in this project allows you to build, test, and automatically effect a change once you push to the Version Control System, which is GitHub in this case. &lt;/p&gt;

&lt;p&gt;To set up GitHub Actions, simply create a &lt;code&gt;.github/workflows&lt;/code&gt; folder, and create the file &lt;code&gt;deploy.yml&lt;/code&gt; in it and paste in the following code:&lt;br&gt;
&lt;/p&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;Terraform Deploy&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="c1"&gt;# or your deployment branch&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;terraform&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;Terraform Apply&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;defaults&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform&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;Checkout code&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@v3&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;Setup Terraform&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;hashicorp/setup-terraform@v3&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;terraform_version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1.8.0&lt;/span&gt;  &lt;span class="c1"&gt;# or your preferred version&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 AWS credentials&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;aws-actions/configure-aws-credentials@v2&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;aws-access-key-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_ACCESS_KEY_ID }}&lt;/span&gt;
        &lt;span class="na"&gt;aws-secret-access-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_SECRET_ACCESS_KEY }}&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;Terraform Init&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;terraform init&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;Terraform Validate&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;terraform validate&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;Terraform Plan&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;terraform plan \&lt;/span&gt;
          &lt;span class="s"&gt;-var="db_user=${{ secrets.DB_USER }}" \&lt;/span&gt;
          &lt;span class="s"&gt;-var="db_password=${{ secrets.DB_PASSWORD }}" \&lt;/span&gt;
          &lt;span class="s"&gt;-var="db_port=${{ secrets.DB_PORT }}"&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;Terraform Apply&lt;/span&gt;
      &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.ref == 'refs/heads/main'&lt;/span&gt;  &lt;span class="c1"&gt;# protect applies to main branch&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;terraform apply -auto-approve \&lt;/span&gt;
          &lt;span class="s"&gt;-var="db_user=${{ secrets.DB_USER }}" \&lt;/span&gt;
          &lt;span class="s"&gt;-var="db_password=${{ secrets.DB_PASSWORD }}" \&lt;/span&gt;
          &lt;span class="s"&gt;-var="db_port=${{ secrets.DB_PORT }}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The GitHub Actions code above performs a set of actions once a push is made to the main branch. It first sets the runner of the GitHub Actions, which is &lt;code&gt;ubuntu-latest&lt;/code&gt;. Next, it sets the folder "terraform" as the default working directory, checks out the code, and sets up (installs) Terraform on the runner. &lt;/p&gt;

&lt;p&gt;To be able to deploy to AWS, you configure your running using your &lt;code&gt;ACCESS_KEY&lt;/code&gt; and &lt;code&gt;SECRET_KEY&lt;/code&gt; that can be gotten from your AWS "Security Credentials" page. Next, it runs &lt;code&gt;terraform init&lt;/code&gt;, which initializes the Terraform environment. Then it validates the code, ensuring the syntax is correct. Next, it runs &lt;code&gt;terraform plan&lt;/code&gt;, which creates a plan of the resources to be created using variables from GitHub secrets. And finally, it applies the code with the same variables from GitHub Secrets.&lt;/p&gt;

&lt;p&gt;For this to work, you have to have your secret set up. To do that, click on the "Settings" tab of your repository:&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%2Fbj4pfu9mqr40b80m13ae.jpeg" 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%2Fbj4pfu9mqr40b80m13ae.jpeg" alt="Actions Tab" width="763" height="40"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Under "Security," click on "Secrets and variables" and select "Actions"&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%2Fzslxwwbp145az1ofhf24.jpeg" 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%2Fzslxwwbp145az1ofhf24.jpeg" alt="Secrets and Variables" width="248" height="147"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lastly, create repository secrets for &lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt; and &lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt;, assigning values from your AWS account, and &lt;code&gt;DB_USER&lt;/code&gt;, &lt;code&gt;DB_PASSWORD&lt;/code&gt;, &lt;code&gt;DB_PORT&lt;/code&gt;, assigning values of your choice. For &lt;code&gt;DB_PORT&lt;/code&gt;, it is advisable to use "5432" as that is the default port of PostgreSQL.&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%2Fsh8cs95bh1xnorncelxn.jpeg" 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%2Fsh8cs95bh1xnorncelxn.jpeg" alt="Secrets and Variables" width="609" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Verifying the Deployment
&lt;/h2&gt;

&lt;p&gt;After completing the deployment, you need to verify that everything is running as expected.&lt;/p&gt;

&lt;p&gt;Start by connecting to the Bastion Host using SSH:&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;eval&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;ssh-agent &lt;span class="nt"&gt;-s&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
ssh-add ~/path/to/key-pair
ssh &lt;span class="nt"&gt;-A&lt;/span&gt; ubuntu@&amp;lt;bastion-ip&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once inside the Bastion Host, check that the monitoring containers are running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker ps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the container for &lt;strong&gt;Grafana&lt;/strong&gt; running.&lt;/p&gt;

&lt;p&gt;Next, open your browser and navigate to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://&amp;lt;bastion-public-ip&amp;gt;:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will take you to the &lt;strong&gt;Grafana dashboard&lt;/strong&gt;. Log in using the default credentials (&lt;code&gt;admin&lt;/code&gt; / &lt;code&gt;admin&lt;/code&gt;), then go to &lt;strong&gt;Settings → Data Sources&lt;/strong&gt;, and add a new Prometheus data source with the following URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://&amp;lt;private-ec2-ip&amp;gt;:9090
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This links Grafana to the Prometheus instance running on the private EC2 instance.&lt;/p&gt;

&lt;p&gt;Once that's done, return to your terminal and SSH into the private EC2 instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh ubuntu@&amp;lt;private_ip&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check that the frontend and backend services are running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker ps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lastly, open the application in your browser by visiting the ALB DNS URL. This is the public-facing entry point to your full-stack application. You should be able to load the frontend and confirm that it connects properly to the backend.&lt;/p&gt;

&lt;p&gt;Everything should now be live and fully functional.&lt;/p&gt;

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

&lt;p&gt;This guide walks through how to deploy a full-stack Dockerized application to AWS using Terraform. You set up the infrastructure from scratch, including VPCs, subnets, security groups, and EC2 instances, and made sure your application could run securely inside a private subnet. You also added a bastion host to help us reach the private instances when needed.&lt;/p&gt;

&lt;p&gt;On top of that, you configured a load balancer for public access and set up monitoring using Prometheus and Grafana. With this in place, you’re able to track system metrics and confirm that everything’s running as expected.&lt;/p&gt;

&lt;p&gt;There are several ways in which this setup can be improved, one of which is including extensive logging within the system and application, but for now, this is a good start. If you enjoyed this article, you can head on to my &lt;a href="https://dev.to/ephraimx"&gt;page&lt;/a&gt; to read more DevOps-related articles.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to Deploy a Full Stack App to Koyeb Using Docker Compose and CircleCI</title>
      <dc:creator>EphraimX</dc:creator>
      <pubDate>Tue, 12 Aug 2025 08:54:17 +0000</pubDate>
      <link>https://dev.to/ephraimx/how-to-deploy-a-full-stack-app-to-koyeb-using-docker-compose-and-circleci-7mk</link>
      <guid>https://dev.to/ephraimx/how-to-deploy-a-full-stack-app-to-koyeb-using-docker-compose-and-circleci-7mk</guid>
      <description>&lt;p&gt;CircleCI offers a developer-friendly and efficient CI/CD experience, making it an excellent choice for automating deployment workflows for containerized applications. In this guide, you'll learn how to deploy a full-stack Docker Compose application to Koyeb using CircleCI.&lt;/p&gt;

&lt;p&gt;We’ll configure a custom pipeline that uses the Koyeb CLI and leverages the Dockerfile-based deployment mechanism. This approach mirrors our previous GitHub Actions, GitLab CI/CD, and Jenkins guides but focuses specifically on CircleCI's environment and capabilities.&lt;/p&gt;

&lt;p&gt;If you haven’t yet reviewed the foundational article that walks through the project setup, Dockerfiles, and Docker Compose configuration, please start there: Deploying a Full Stack App to Koyeb Using Docker Compose and GitHub Actions&lt;/p&gt;

&lt;p&gt;Once you're familiar with the application structure and build context, proceed with this article to integrate CircleCI into your workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;Obtaining Your Koyeb API Token&lt;/li&gt;
&lt;li&gt;Configuring CircleCI Environment Variables&lt;/li&gt;
&lt;li&gt;CircleCI Pipeline Configuration&lt;/li&gt;
&lt;li&gt;Deploying to Koyeb Using CircleCI&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before diving into the CircleCI setup and deployment process, make sure you have the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Familiarity with the Full Stack Application Setup:&lt;/strong&gt; This article builds on the foundation laid in the previous article, How to Deploy a Full Stack App to Koyeb Using Docker Compose and GitHub Actions. It’s recommended to review that article first for context on the application structure and Docker setup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CircleCI Account:&lt;/strong&gt; You need an active CircleCI account connected to your GitHub repository where the application code resides. If you don’t have one, sign up at &lt;a href="https://circleci.com" rel="noopener noreferrer"&gt;circleci.com&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Koyeb Account:&lt;/strong&gt; A Koyeb account is required to deploy your application. Simply create an account at &lt;a href="https://www.koyeb.com" rel="noopener noreferrer"&gt;koyeb.com&lt;/a&gt; if you don’t have one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Koyeb API Token:&lt;/strong&gt; You will need a Koyeb API token for authentication when deploying via CircleCI. Instructions for obtaining this token will be covered in the next section.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Basic Understanding of CircleCI Pipelines:&lt;/strong&gt; If you are new to CircleCI pipelines or configuration, consider reviewing the official &lt;a href="https://circleci.com/docs/" rel="noopener noreferrer"&gt;CircleCI documentation&lt;/a&gt; or refer to my article on setting up CI/CD pipelines with CircleCI.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Obtaining Your Koyeb API Token
&lt;/h2&gt;

&lt;p&gt;To deploy your application to Koyeb using CircleCI, you'll need a Koyeb API token for authentication. Follow these steps to generate one:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Log in to your Koyeb account&lt;/strong&gt;: Visit &lt;a href="https://app.koyeb.com" rel="noopener noreferrer"&gt;app.koyeb.com&lt;/a&gt; and sign in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access Account Settings&lt;/strong&gt;: Click on your profile icon in the top-right corner and select &lt;strong&gt;"Account Settings"&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Navigate to API Access Tokens&lt;/strong&gt;: In the left sidebar, click on &lt;strong&gt;"API Access Tokens"&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create a New Token&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Click the &lt;strong&gt;"Create API Access Token"&lt;/strong&gt; button.&lt;/li&gt;
&lt;li&gt;Provide a &lt;strong&gt;name&lt;/strong&gt; and &lt;strong&gt;description&lt;/strong&gt; for the token to help identify its purpose.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;"Create"&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Save the Token&lt;/strong&gt;: Once generated, &lt;strong&gt;copy&lt;/strong&gt; the token immediately. For security reasons, you won't be able to view it again.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Adding the Koyeb API Token to CircleCI
&lt;/h2&gt;

&lt;p&gt;To securely store and use your Koyeb API token in your CircleCI pipeline, add it as an environment variable:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Log in to CircleCI&lt;/strong&gt;: Go to &lt;a href="https://circleci.com" rel="noopener noreferrer"&gt;circleci.com&lt;/a&gt; and sign in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Navigate to Your Project&lt;/strong&gt;: From the dashboard, select the project where your application resides.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access Project Settings&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Click on the gear icon next to your project name.&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;"Project Settings"&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add an Environment Variable&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;In the left sidebar, click on &lt;strong&gt;"Environment Variables"&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;"Add Variable"&lt;/strong&gt; button.&lt;/li&gt;
&lt;li&gt;In the &lt;strong&gt;"Name"&lt;/strong&gt; field, enter &lt;code&gt;KOYEB_API_TOKEN&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;In the &lt;strong&gt;"Value"&lt;/strong&gt; field, paste the API token you copied earlier.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;"Add Variable"&lt;/strong&gt; to save.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This environment variable will now be accessible in your CircleCI jobs and can be used to authenticate with the Koyeb API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up CircleCI Configuration
&lt;/h2&gt;

&lt;p&gt;First, create a &lt;code&gt;.circleci&lt;/code&gt; directory at the root of your project if it doesn't already exist:&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;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; .circleci
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, inside this directory, create a file named &lt;code&gt;config.yml&lt;/code&gt; and paste the following code:&lt;br&gt;
&lt;/p&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="m"&gt;2.1&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;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;docker&lt;/span&gt;&lt;span class="pi"&gt;:&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;ubuntu:24.04&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="s"&gt;checkout&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&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 Curl and Koyeb CLI&lt;/span&gt;
          &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;apt-get update&lt;/span&gt;
            &lt;span class="s"&gt;apt-get install -y curl&lt;/span&gt;
            &lt;span class="s"&gt;curl -fsSL https://raw.githubusercontent.com/koyeb/koyeb-cli/master/install.sh | sh&lt;/span&gt;
            &lt;span class="s"&gt;export PATH=$HOME/.koyeb/bin:$PATH&lt;/span&gt;
            &lt;span class="s"&gt;export KOYEB_TOKEN=${KOYEB_API_TOKEN}&lt;/span&gt;
            &lt;span class="s"&gt;koyeb app create glowberry-tax-structure-simulator-glabcicd-docker-compose-koyeb || echo "App already exists"&lt;/span&gt;
            &lt;span class="s"&gt;koyeb service create glowberry-tax-structure-simulator-glabcicd-docker-compose-koyeb \&lt;/span&gt;
              &lt;span class="s"&gt;--app glowberry-tax-structure-simulator-glabcicd-docker-compose-koyeb \&lt;/span&gt;
              &lt;span class="s"&gt;--git github.com/EphraimX/glowberry-global-tax-structure-simulator-gha-docker-compose-koyeb \&lt;/span&gt;
              &lt;span class="s"&gt;--instance-type free \&lt;/span&gt;
              &lt;span class="s"&gt;--git-builder docker \&lt;/span&gt;
              &lt;span class="s"&gt;--git-docker-dockerfile Dockerfile.koyeb \&lt;/span&gt;
              &lt;span class="s"&gt;--port 3000:http \&lt;/span&gt;
              &lt;span class="s"&gt;--route /:3000 \&lt;/span&gt;
              &lt;span class="s"&gt;--privileged&lt;/span&gt;

&lt;span class="na"&gt;workflows&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build-and-deploy-to-koyeb&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation of the CircleCI Configuration
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;version: 2.1&lt;/strong&gt; Specifies the CircleCI configuration version used.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;jobs:&lt;/strong&gt; Defines a set of jobs CircleCI will run. Here, we have a single job named &lt;code&gt;deploy&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;deploy job:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;docker:&lt;/strong&gt;
Runs the job inside an Ubuntu 24.04 Docker container.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;steps:&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;checkout&lt;/code&gt;: Checks out your repository code so the job can use it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;run&lt;/code&gt; step named &lt;em&gt;Install Curl and Koyeb CLI&lt;/em&gt;:

&lt;ul&gt;
&lt;li&gt;Updates package lists and installs &lt;code&gt;curl&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Downloads and installs the Koyeb CLI tool.&lt;/li&gt;
&lt;li&gt;Updates the &lt;code&gt;PATH&lt;/code&gt; to include the Koyeb CLI.&lt;/li&gt;
&lt;li&gt;Sets the environment variable &lt;code&gt;KOYEB_TOKEN&lt;/code&gt; from the CircleCI environment variable &lt;code&gt;${KOYEB_API_TOKEN}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Runs &lt;code&gt;koyeb app create&lt;/code&gt; to create the app if it doesn't already exist (ignores error if it does).&lt;/li&gt;
&lt;li&gt;Runs &lt;code&gt;koyeb service create&lt;/code&gt; with detailed parameters to deploy the app and service to Koyeb.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;workflows:&lt;/strong&gt; Defines the workflow that triggers jobs. Here, it runs the &lt;code&gt;deploy&lt;/code&gt; job under the name &lt;code&gt;build-and-deploy-to-koyeb&lt;/code&gt;.&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pushing to GitHub and Monitoring Your Deployment
&lt;/h2&gt;

&lt;p&gt;Once your &lt;code&gt;.circleci/config.yml&lt;/code&gt; file is ready and committed to your project repository, you can trigger the deployment process by pushing your code to GitHub.&lt;/p&gt;

&lt;h3&gt;
  
  
  Steps to Trigger Deployment
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Commit your changes:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   git add .circleci/config.yml
   git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Add CircleCI config for Koyeb deployment"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Push to your repository:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   git push origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;main&lt;/code&gt; with your default branch if different.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monitoring the Deployment on CircleCI
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;After pushing, CircleCI will automatically detect the changes and start the pipeline based on your config file.&lt;/li&gt;
&lt;li&gt;To monitor the deployment progress:

&lt;ol&gt;
&lt;li&gt;Log in to your CircleCI dashboard at &lt;a href="https://app.circleci.com/" rel="noopener noreferrer"&gt;https://app.circleci.com/&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Navigate to your project’s pipeline page.&lt;/li&gt;
&lt;li&gt;Click on the running job (usually labeled &lt;code&gt;deploy&lt;/code&gt; under the workflow name &lt;code&gt;build-and-deploy-to-koyeb&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;View detailed logs of each step in the job, including:

&lt;ul&gt;
&lt;li&gt;Checkout your repo code.&lt;/li&gt;
&lt;li&gt;Installation of curl and the Koyeb CLI.&lt;/li&gt;
&lt;li&gt;Koyeb app and service creation commands.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ol&gt;

&lt;/li&gt;

&lt;li&gt;Any errors during the deployment will appear here, allowing you to troubleshoot quickly.&lt;/li&gt;

&lt;/ul&gt;

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

&lt;p&gt;In this guide, you’ve learned how to automate the deployment of a full-stack application to Koyeb using CircleCI and Docker Compose. By configuring your CircleCI pipeline with the Koyeb CLI, you ensure that every push to your repository triggers a seamless build and deployment process.&lt;/p&gt;

&lt;p&gt;This approach eliminates manual deployment steps, reducing errors and accelerating your development workflow. With the environment variables securely stored in CircleCI and the deployment status easily monitored via the CircleCI dashboard, managing your application lifecycle becomes efficient and transparent.&lt;/p&gt;

&lt;p&gt;If you want to explore other CI/CD tools, consider our articles on GitHub Actions, GitLab CI/CD, and Jenkins integrations with Koyeb.&lt;/p&gt;

&lt;p&gt;Found this guide helpful? Follow &lt;a href="https://github.com/EphraimX" rel="noopener noreferrer"&gt;EphraimX&lt;/a&gt; for more hands-on DevOps walkthroughs. You can also connect with me on &lt;a href="https://www.linkedin.com/in/oghenefejiro-esosuota" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; or explore more of my work on my &lt;a href="https://ephraimx.github.io/" rel="noopener noreferrer"&gt;portfolio&lt;/a&gt;.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to Deploy a Frontend App to Vercel Using Jenkins</title>
      <dc:creator>EphraimX</dc:creator>
      <pubDate>Tue, 12 Aug 2025 08:17:49 +0000</pubDate>
      <link>https://dev.to/ephraimx/how-to-deploy-a-frontend-app-to-vercel-using-jenkins-40aa</link>
      <guid>https://dev.to/ephraimx/how-to-deploy-a-frontend-app-to-vercel-using-jenkins-40aa</guid>
      <description>&lt;p&gt;While most frontend teams rely on Vercel’s default Git integrations for automatic deployments, there are times when you need more control. Jenkins gives you that power—with fully customizable pipelines, conditional logic, and integrations tailored to your workflow.&lt;/p&gt;

&lt;p&gt;In this article, you’ll learn how to set up a Jenkins pipeline that automatically builds and deploys a frontend application to Vercel whenever changes are pushed to your main branch.&lt;/p&gt;

&lt;p&gt;We’ll cover the entire workflow, including securely managing your Vercel token, writing your Jenkinsfile, and verifying the deployment on the Vercel dashboard—all without relying on Vercel’s default GitHub or GitLab integrations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;
Setting Up Jenkins for CI/CD

&lt;ul&gt;
&lt;li&gt;Installing Jenkins via Docker (Recommended)&lt;/li&gt;
&lt;li&gt;Installing Jenkins via Script (Linux)&lt;/li&gt;
&lt;li&gt;First Time Setup&lt;/li&gt;
&lt;li&gt;Creating a Jenkins Pipeline with Git Polling&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
Creating the Jenkinsfile for Vercel Frontend Deployment

&lt;ul&gt;
&lt;li&gt;Explaining the Jenkinsfile&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Setting Up the Vercel Token in Jenkins&lt;/li&gt;
&lt;li&gt;Deploying and Testing the Frontend Application&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before diving in, ensure you’ve got the following ready:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;a href="https://vercel.com/" rel="noopener noreferrer"&gt;Vercel&lt;/a&gt; account with an existing project.&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://www.jenkins.io/" rel="noopener noreferrer"&gt;Jenkins&lt;/a&gt; setup (self-hosted or via CI providers).&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;Vercel token&lt;/strong&gt; with project-level scope and an appropriate expiry set.
(If you haven’t generated one before, refer to the A Practical Guide to Deploying Frontend Apps on Vercel Using GitHub Actions article for a detailed walkthrough.&lt;/li&gt;
&lt;li&gt;Node.js installed in your Jenkins environment.&lt;/li&gt;
&lt;li&gt;Familiarity with the structure of your frontend app.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’re using the same TypeScript-based frontend application introduced in the A Practical Guide to Deploying Frontend Apps on Vercel Using GitHub Actions. Read through that article first to get a solid understanding of the app layout before continuing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up Jenkins for CI/CD
&lt;/h2&gt;

&lt;p&gt;Before we can deploy anything with Jenkins, we need to make sure it's up and running correctly. Below is a guide on how to install Jenkins, configure your admin account, and create a pipeline that polls a Git repository.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1: Installing Jenkins via Docker (Recommended)
&lt;/h3&gt;

&lt;p&gt;If you’re comfortable with Docker, this is the fastest way to get started:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:8080 &lt;span class="nt"&gt;-p&lt;/span&gt; 50000:50000 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; jenkins_home:/var/jenkins_home &lt;span class="se"&gt;\&lt;/span&gt;
  jenkins/jenkins:lts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once running, open &lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt; in your browser.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2: Installing Jenkins via Script (Linux)
&lt;/h3&gt;

&lt;p&gt;If you're installing Jenkins natively:&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="c"&gt;# Add Jenkins repository and key&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://pkg.jenkins.io/debian/jenkins.io-2023.key | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  /usr/share/keyrings/jenkins-keyring.asc &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null

&lt;span class="nb"&gt;echo &lt;/span&gt;deb &lt;span class="o"&gt;[&lt;/span&gt;signed-by&lt;span class="o"&gt;=&lt;/span&gt;/usr/share/keyrings/jenkins-keyring.asc] &lt;span class="se"&gt;\&lt;/span&gt;
  https://pkg.jenkins.io/debian binary/ | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  /etc/apt/sources.list.d/jenkins.list &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null

&lt;span class="c"&gt;# Update and install&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;jenkins &lt;span class="nt"&gt;-y&lt;/span&gt;

&lt;span class="c"&gt;# Start Jenkins&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start jenkins
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Jenkins will be accessible at &lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;N.B:&lt;/strong&gt; You have to have Java installed on your machine.&lt;/p&gt;

&lt;h3&gt;
  
  
  First Time Setup
&lt;/h3&gt;

&lt;p&gt;After installation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Unlock Jenkins&lt;/strong&gt;:
Visit &lt;code&gt;http://localhost:8080&lt;/code&gt;. You’ll see a screen asking for an admin password. Run:
&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 cat&lt;/span&gt; /var/lib/jenkins/secrets/initialAdminPassword
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install Plugins&lt;/strong&gt;:
Choose &lt;strong&gt;Install Suggested Plugins&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create Admin User&lt;/strong&gt;:
Set up your Jenkins username, password, and full name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confirm URL&lt;/strong&gt;:
You can leave the Jenkins URL as &lt;code&gt;http://localhost:8080&lt;/code&gt; unless you’re using a custom domain.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Creating a Jenkins Pipeline with Git Polling
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create a New Jenkins Pipeline Job&lt;/strong&gt;:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;From your Jenkins dashboard, click &lt;strong&gt;New Item&lt;/strong&gt;.*&lt;/li&gt;
&lt;li&gt;Enter a name for your job (e.g., &lt;code&gt;frontend-deploy-vercel&lt;/code&gt;) and select &lt;strong&gt;Pipeline&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;OK&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Configure the Pipeline&lt;/strong&gt;:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;In the &lt;strong&gt;Pipeline&lt;/strong&gt; job config, scroll to &lt;strong&gt;Pipeline → Definition&lt;/strong&gt;, and set it to &lt;code&gt;Pipeline script from SCM&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Set SCM to &lt;strong&gt;Git&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Paste your repository URL (GitHub or GitLab)&lt;/li&gt;
&lt;li&gt;(Optional but recommended) Add credentials if the repo is private&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Polling the Repository&lt;/strong&gt;:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Scroll down to &lt;strong&gt;Build Triggers&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Tick &lt;strong&gt;Poll SCM&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Use the schedule H/5 * * * * to check for changes every 5 minutes (You can adjust this later for performance)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once this setup is complete, Jenkins will automatically poll your repo and run the defined pipeline when changes are pushed to the main branch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the Jenkinsfile for Vercel Frontend Deployment
&lt;/h2&gt;

&lt;p&gt;To automate your frontend deployments to Vercel, create a &lt;code&gt;Jenkinsfile&lt;/code&gt; at the &lt;strong&gt;root of your repository&lt;/strong&gt; and include the following configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;pipeline&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt;

  &lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;VERCEL_TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'VERCEL_TOKEN'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;stages&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Build and Deploy Frontend Service'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'frontend'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'npm install'&lt;/span&gt;
          &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'npm run build'&lt;/span&gt;
          &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'npx vercel --prod --token $VERCEL_TOKEN --yes'&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;success&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'Frontend deployed successfully to Vercel.'&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;failure&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'Frontend deployment failed.'&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: This assumes the vercel CLI is already installed globally in your Jenkins agent. If not,&lt;code&gt;add npm install -g vercel&lt;/code&gt; before the deploy step.&lt;/p&gt;

&lt;h3&gt;
  
  
  Explaining the Jenkinsfile
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pipeline { ... }&lt;/code&gt;: Defines the structure of the Jenkins pipeline in a declarative format. This setup helps automate and manage CI/CD tasks in a readable and maintainable way.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;agent any&lt;/code&gt;: Specifies that this pipeline can run on any available Jenkins agent. This provides flexibility for most general-purpose use cases.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;environment { VERCEL_TOKEN = credentials('VERCEL_TOKEN') }&lt;/code&gt;: Loads the &lt;code&gt;VERCEL_TOKEN&lt;/code&gt; from Jenkins’ secure credentials store. This token is necessary for authenticating with Vercel’s CLI. By storing it as a secret credential in Jenkins, you keep your deployment pipeline secure and avoid hardcoding sensitive data.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;stages { ... }&lt;/code&gt;: Defines the main steps in the pipeline.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;stage('Build and Deploy Frontend Service')&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;dir('frontend')&lt;/code&gt;: Changes directory into the &lt;code&gt;frontend&lt;/code&gt; folder, which contains the codebase for the frontend application.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sh 'npm install'&lt;/code&gt;: Installs all required dependencies.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sh 'npm run build'&lt;/code&gt;: Compiles the application into production-ready static assets.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sh 'npx vercel --prod --token $VERCEL_TOKEN --yes'&lt;/code&gt;: Deploys the compiled application to Vercel using the CLI. The &lt;code&gt;--prod&lt;/code&gt; flag ensures it's a production deployment, &lt;code&gt;--yes&lt;/code&gt; bypasses interactive prompts, and the &lt;code&gt;--token&lt;/code&gt; ensures authenticated access.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;post { ... }&lt;/code&gt;: Handles actions that should run after the pipeline completes:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;success&lt;/code&gt;: Logs a confirmation message if the deployment is successful.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;failure&lt;/code&gt;: Outputs an error message to aid in troubleshooting if the deployment fails.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Once this is in place, every time your pipeline runs, Jenkins will automatically build and deploy your frontend service to Vercel. Next, we’ll cover how to securely add your Vercel token to Jenkins using the credentials store.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up the Vercel Token in Jenkins
&lt;/h2&gt;

&lt;p&gt;For Jenkins to deploy your frontend application to Vercel, you'll need to store your Vercel token securely using Jenkins' credentials manager.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Access Jenkins Credentials
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;From your Jenkins dashboard, go to &lt;strong&gt;Manage Jenkins&lt;/strong&gt; → &lt;strong&gt;Credentials&lt;/strong&gt; → &lt;strong&gt;(global)&lt;/strong&gt; → &lt;strong&gt;Add Credentials&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 2: Add the Vercel Token
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;In the &lt;strong&gt;Add Credentials&lt;/strong&gt; dialog:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Kind&lt;/strong&gt;: Choose &lt;strong&gt;Secret text&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secret&lt;/strong&gt;: Paste your Vercel token.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ID&lt;/strong&gt;: Enter &lt;code&gt;VERCEL_TOKEN&lt;/code&gt; (ensure it matches what you’ve referenced in your Jenkinsfile).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Description&lt;/strong&gt;: (Optional) e.g., &lt;code&gt;Vercel Deployment Token&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;OK&lt;/strong&gt; to save the token.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;You can obtain your token from the &lt;a href="https://vercel.com/dashboard" rel="noopener noreferrer"&gt;Vercel dashboard&lt;/a&gt;:&lt;br&gt;
Click your profile avatar → &lt;strong&gt;Account Settings&lt;/strong&gt; → &lt;strong&gt;Tokens&lt;/strong&gt;, then create a new one with a defined name, scope (recommended: project-specific), and expiration date.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once stored, Jenkins will automatically inject the token into your pipeline when referenced using &lt;code&gt;credentials('VERCEL_TOKEN')&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying and Testing the Frontend Application
&lt;/h2&gt;

&lt;p&gt;After setting up the &lt;code&gt;Jenkinsfile&lt;/code&gt; and securely storing your Vercel token, you can move ahead with deploying your frontend application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Execute the Build
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Click &lt;strong&gt;Build Now&lt;/strong&gt; to kick off the process.&lt;/li&gt;
&lt;li&gt;Monitor progress in the &lt;strong&gt;Build History&lt;/strong&gt; → select the build → &lt;strong&gt;Console Output&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Jenkins will:&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Install frontend dependencies in the &lt;code&gt;frontend/&lt;/code&gt; directory.&lt;/li&gt;
&lt;li&gt;Build the project using &lt;code&gt;npm run build&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Deploy to Vercel using the CLI and the stored token.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 2: Confirm Deployment
&lt;/h3&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%2Fmuzw4908oro7uzf9h5al.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%2Fmuzw4908oro7uzf9h5al.png" alt="Vercel Dashboard" width="800" height="353"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Head to the &lt;a href="https://vercel.com/dashboard" rel="noopener noreferrer"&gt;Vercel dashboard&lt;/a&gt; and locate your project.&lt;/li&gt;
&lt;li&gt;Confirm the successful deployment and access the production URL.&lt;/li&gt;
&lt;li&gt;You can also inspect the deployment logs on Vercel for any additional diagnostics.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Using Jenkins to deploy frontend applications to Vercel brings structure and consistency to your release process. While Vercel’s Git integration works out of the box, Jenkins offers additional room to scale: conditional builds, preview environments, and integration with testing or linting tools.&lt;/p&gt;

&lt;p&gt;In this article, we walked through setting up Jenkins, creating a pipeline job, storing Vercel tokens securely, and automating deployments with a structured Jenkinsfile. You also learned how to monitor builds and verify your production release via the Vercel dashboard.&lt;/p&gt;

&lt;p&gt;This setup is a solid starting point for teams that want tight control over their CI/CD flows while still leveraging powerful hosting platforms like Vercel.&lt;/p&gt;

&lt;p&gt;Found this guide useful? Follow &lt;a href="https://github.com/EphraimX" rel="noopener noreferrer"&gt;EphraimX&lt;/a&gt; for more practical DevOps guides and automation tips. You can also connect with me on &lt;a href="https://www.linkedin.com/in/oghenefejiro-esosuota" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; or explore more of my projects on &lt;a href="https://ephraimx.github.io" rel="noopener noreferrer"&gt;my portfolio&lt;/a&gt;.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to Deploy a Python Backend to Fly.io Using Jenkins</title>
      <dc:creator>EphraimX</dc:creator>
      <pubDate>Tue, 12 Aug 2025 08:15:57 +0000</pubDate>
      <link>https://dev.to/ephraimx/how-to-deploy-a-python-backend-to-flyio-using-jenkins-1lh7</link>
      <guid>https://dev.to/ephraimx/how-to-deploy-a-python-backend-to-flyio-using-jenkins-1lh7</guid>
      <description>&lt;p&gt;Jenkins is one of the most trusted CI/CD tools for developers who want full control over their automation workflows. In this guide, you’ll learn how to set up a Jenkins pipeline that automatically deploys a Python backend application to Fly.io anytime there’s a change in your main branch.&lt;/p&gt;

&lt;p&gt;This process is ideal if you want to maintain complete control—no hidden processes, no magic buttons—just a clear, auditable, and customizable CI/CD pipeline.&lt;/p&gt;

&lt;p&gt;In this guide, you’ll walk through the exact setup, from creating your Jenkinsfile to storing sensitive tokens securely, and finally testing your deployment live on Fly.io.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;
Setting Up Jenkins for CI/CD

&lt;ul&gt;
&lt;li&gt;Option 1: Installing Jenkins via Docker (Recommended)&lt;/li&gt;
&lt;li&gt;Option 2: Installing Jenkins via Script (Linux)&lt;/li&gt;
&lt;li&gt;First Time Setup&lt;/li&gt;
&lt;li&gt;Creating a Jenkins Pipeline with Git Polling&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Creating the Jenkinsfile for Fly.io Deployment&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Explaining the Jenkinsfile&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Setting Up the Fly.io API Token in Jenkins&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Deploying and Testing the Backend Application&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Conclusion&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;To follow along with this tutorial, make sure you have the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;a href="https://fly.io/" rel="noopener noreferrer"&gt;Fly.io&lt;/a&gt; account.&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://www.jenkins.io/" rel="noopener noreferrer"&gt;Jenkins&lt;/a&gt; instance up and running (locally, on a server, or via a CI service).&lt;/li&gt;
&lt;li&gt;A valid &lt;strong&gt;Fly.io API token&lt;/strong&gt;. You can create one from your Fly.io dashboard under &lt;strong&gt;Access Tokens&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Basic understanding of your application's structure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This article uses the same Python backend from our How to Set Up CI/CD for a Python Backend Application on Fly.io Using GitHub Actions. If you haven’t already, go through that article to understand how the application works and how the &lt;code&gt;Dockerfile&lt;/code&gt; is structured. That knowledge will be important when setting up Jenkins correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up Jenkins for CI/CD
&lt;/h2&gt;

&lt;p&gt;Before we can deploy anything with Jenkins, we need to make sure it's up and running correctly. Below is a guide on how to install Jenkins, configure your admin account, and create a pipeline that polls a Git repository.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1: Installing Jenkins via Docker (Recommended)
&lt;/h3&gt;

&lt;p&gt;If you’re comfortable with Docker, this is the fastest way to get started:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:8080 &lt;span class="nt"&gt;-p&lt;/span&gt; 50000:50000 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; jenkins_home:/var/jenkins_home &lt;span class="se"&gt;\&lt;/span&gt;
  jenkins/jenkins:lts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once running, open &lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt; in your browser.&lt;/p&gt;




&lt;h3&gt;
  
  
  Option 2: Installing Jenkins via Script (Linux)
&lt;/h3&gt;

&lt;p&gt;If you're installing Jenkins natively:&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="c"&gt;# Add Jenkins repository and key&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://pkg.jenkins.io/debian/jenkins.io-2023.key | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  /usr/share/keyrings/jenkins-keyring.asc &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null

&lt;span class="nb"&gt;echo &lt;/span&gt;deb &lt;span class="o"&gt;[&lt;/span&gt;signed-by&lt;span class="o"&gt;=&lt;/span&gt;/usr/share/keyrings/jenkins-keyring.asc] &lt;span class="se"&gt;\&lt;/span&gt;
  https://pkg.jenkins.io/debian binary/ | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  /etc/apt/sources.list.d/jenkins.list &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null

&lt;span class="c"&gt;# Update and install&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;jenkins &lt;span class="nt"&gt;-y&lt;/span&gt;

&lt;span class="c"&gt;# Start Jenkins&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start jenkins
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Jenkins will be accessible at &lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;N.B.:&lt;/strong&gt; You have to have Java installed on your machine.&lt;/p&gt;

&lt;h3&gt;
  
  
  First Time Setup
&lt;/h3&gt;

&lt;p&gt;After installation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Unlock Jenkins&lt;/strong&gt;:
Visit &lt;code&gt;http://localhost:8080&lt;/code&gt;. You’ll see a screen asking for an admin password. Run:
&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 cat&lt;/span&gt; /var/lib/jenkins/secrets/initialAdminPassword
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install Plugins&lt;/strong&gt;:
Choose &lt;strong&gt;Install Suggested Plugins&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create Admin User&lt;/strong&gt;:
Set up your Jenkins username, password, and full name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confirm URL&lt;/strong&gt;:
You can leave the Jenkins URL as &lt;code&gt;http://localhost:8080&lt;/code&gt; unless you’re using a custom domain.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Creating a Jenkins Pipeline with Git Polling
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create a New Jenkins Pipeline Job&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;From your Jenkins dashboard, click &lt;strong&gt;New Item&lt;/strong&gt;.*&lt;/li&gt;
&lt;li&gt;Enter a name for your job (e.g., &lt;code&gt;backend-deploy-flyio&lt;/code&gt;) and select &lt;strong&gt;Pipeline&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;OK&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configure the Pipeline&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;In the &lt;strong&gt;Pipeline&lt;/strong&gt; job config, scroll to &lt;strong&gt;Pipeline → Definition&lt;/strong&gt;, and set it to &lt;code&gt;Pipeline script from SCM&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Set SCM to &lt;strong&gt;Git&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Paste your repository URL (GitHub or GitLab)&lt;/li&gt;
&lt;li&gt;(Optional but recommended) Add credentials if the repo is private&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Polling the Repository&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Scroll down to &lt;strong&gt;Build Triggers&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Tick &lt;strong&gt;Poll SCM&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Use the schedule H/5 * * * * to check for changes every 5 minutes (You can adjust this later for performance)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once this setup is complete, Jenkins will automatically poll your repo and run the defined pipeline when changes are pushed to the main branch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the Jenkinsfile for Fly.io Deployment
&lt;/h2&gt;

&lt;p&gt;To configure Jenkins to deploy your Python backend service to Fly.io, you need to create a file named &lt;code&gt;Jenkinsfile&lt;/code&gt; in the &lt;strong&gt;root directory&lt;/strong&gt; of your repository. This file defines the steps Jenkins will execute as part of the pipeline.&lt;/p&gt;

&lt;p&gt;Paste the following content into your &lt;code&gt;Jenkinsfile&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;pipeline&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt;

  &lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;FLY_API_TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'FLY_API_TOKEN'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;stages&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Build and Deploy Backend Service'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'backend'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'flyctl deploy --remote-only'&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;success&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'Backend deployed successfully to Fly.io.'&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;failure&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'Backend deployment failed.'&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explaining the Jenkinsfile
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pipeline { ... }&lt;/code&gt;: This is a declarative Jenkins pipeline. It provides a clear structure for defining the build, test, and deployment lifecycle of your application.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;agent any&lt;/code&gt;: Specifies that the pipeline can run on any available Jenkins agent. This is the default setting and is appropriate unless you are working with a customized or restricted build environment.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;environment { FLY_API_TOKEN = credentials('FLY_API_TOKEN') }&lt;/code&gt;: This section declares environment variables required during the pipeline execution. The &lt;code&gt;credentials()&lt;/code&gt; function pulls the &lt;code&gt;FLY_API_TOKEN&lt;/code&gt; from Jenkins' credentials store, ensuring that your deployment token is not exposed in plain text. You will need to configure this credential in Jenkins, which we will cover later.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;stages { ... }&lt;/code&gt;: Defines the sequential stages that Jenkins will execute.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;stage('Build and Deploy Backend Service')&lt;/code&gt;: This stage handles the core deployment process.

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;dir('backend')&lt;/code&gt;: Navigates into the &lt;code&gt;backend&lt;/code&gt; directory of your repository, where the backend application code resides.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sh 'flyctl deploy --remote-only'&lt;/code&gt;: Executes the Fly.io deployment command using the &lt;code&gt;flyctl&lt;/code&gt; CLI. The &lt;code&gt;--remote-only&lt;/code&gt; flag ensures that the application is packaged locally but built and deployed on Fly.io’s infrastructure, reducing dependencies on your local build environment.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;post { ... }&lt;/code&gt;: This block defines actions that should be executed after the pipeline has finished.

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;success&lt;/code&gt;: Prints a message to the console when the deployment succeeds.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;failure&lt;/code&gt;: Prints a message when the deployment fails, which can assist in diagnosing issues during execution.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;With this setup in place, Jenkins will be able to automate the process of deploying your backend application to Fly.io whenever a new change is pushed to your repository. In the next section, we will walk you through the secure process of adding the &lt;code&gt;FLY_API_TOKEN&lt;/code&gt; to Jenkins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up the Fly.io API Token in Jenkins
&lt;/h2&gt;

&lt;p&gt;To enable automated deployments to Fly.io from Jenkins, you need to securely add your Fly.io API token to the Jenkins credentials system. Follow these steps:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Access the Credentials Manager
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Open your Jenkins dashboard.&lt;/li&gt;
&lt;li&gt;Navigate to &lt;strong&gt;Manage Jenkins&lt;/strong&gt; → &lt;strong&gt;Credentials&lt;/strong&gt; → &lt;strong&gt;(global)&lt;/strong&gt; → &lt;strong&gt;Add Credentials&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 2: Add the Fly.io Token
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;In the &lt;strong&gt;Add Credentials&lt;/strong&gt; form:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Kind&lt;/strong&gt;: Select &lt;strong&gt;Secret text&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secret&lt;/strong&gt;: Paste your Fly.io API token.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ID&lt;/strong&gt;: Enter &lt;code&gt;FLY_API_TOKEN&lt;/code&gt; (make sure it matches the ID used in your Jenkinsfile).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Description&lt;/strong&gt;: (Optional) Add something like &lt;code&gt;Fly.io API Token&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;OK&lt;/strong&gt; to save the token.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;You can retrieve your Fly.io token by running &lt;code&gt;fly auth token&lt;/code&gt; in your terminal after authenticating via &lt;code&gt;fly auth login&lt;/code&gt;, or you can generate one from your &lt;a href="https://fly.io/dashboard" rel="noopener noreferrer"&gt;Fly.io dashboard&lt;/a&gt; under &lt;strong&gt;Account Settings → Access Tokens&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This credential will be securely accessed in the Jenkins pipeline using the &lt;code&gt;credentials('FLY_API_TOKEN')&lt;/code&gt; directive inside the environment block.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying and Testing the Backend Application
&lt;/h2&gt;

&lt;p&gt;Once you’ve created your &lt;code&gt;Jenkinsfile&lt;/code&gt; at the root of your repository and configured your Fly.io token, you’re ready to trigger the deployment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Run the Pipeline
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Click &lt;strong&gt;Build Now&lt;/strong&gt; to trigger your first run.&lt;/li&gt;
&lt;li&gt;Navigate to the &lt;strong&gt;Build History&lt;/strong&gt; and click the build number to view logs.&lt;/li&gt;
&lt;li&gt;If configured correctly, you’ll see Jenkins:

&lt;ol&gt;
&lt;li&gt;Install Fly CLI&lt;/li&gt;
&lt;li&gt;Enter the &lt;code&gt;backend/&lt;/code&gt; directory&lt;/li&gt;
&lt;li&gt;Deploy your backend service to Fly.io using &lt;code&gt;flyctl deploy --remote-only&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 2: Verify Deployment
&lt;/h3&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%2F9grft0t2gyx608xroips.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%2F9grft0t2gyx608xroips.png" alt="Fly.io Dashboard" width="800" height="353"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Visit your Fly.io &lt;a href="https://fly.io/dashboard" rel="noopener noreferrer"&gt;dashboard&lt;/a&gt; and confirm the deployment under the targeted app.&lt;/li&gt;
&lt;li&gt;You can also run &lt;code&gt;fly status&lt;/code&gt; locally to inspect the deployment.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Integrating Jenkins into your Fly.io deployment pipeline gives you more than just automation—it offers control. By managing your own CI/CD infrastructure, you can introduce custom build logic, stage-specific testing, and gated deploy conditions, all without compromising flexibility.&lt;/p&gt;

&lt;p&gt;In this guide, you learned how to install Jenkins, connect your Git repository, and configure a &lt;code&gt;Jenkinsfile&lt;/code&gt; that handles your backend deployment with Fly.io. You also saw how to monitor builds directly from the Jenkins dashboard and verify successful deployments via the Fly.io console.&lt;/p&gt;

&lt;p&gt;With this foundation in place, you're better equipped to scale your CI/CD setup, customize build stages, and adopt a more production-grade engineering workflow.&lt;/p&gt;

&lt;p&gt;If this helped you, consider following &lt;a href="https://github.com/EphraimX" rel="noopener noreferrer"&gt;EphraimX&lt;/a&gt; on GitHub for more articles and open-source automation projects. You can also follow me on &lt;a href="https://www.linkedin.com/in/oghenefejiro-esosuota" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; and explore more of my work on &lt;a href="https://ephraimx.github.io" rel="noopener noreferrer"&gt;my portfolio&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>jenkins</category>
      <category>python</category>
      <category>devops</category>
    </item>
    <item>
      <title>How to Deploy a Full Stack Application to Koyeb Using Docker Compose, Terraform, and Jenkins</title>
      <dc:creator>EphraimX</dc:creator>
      <pubDate>Mon, 23 Jun 2025 13:39:00 +0000</pubDate>
      <link>https://dev.to/ephraimx/how-to-deploy-a-full-stack-application-to-koyeb-using-docker-compose-terraform-and-jenkins-cmj</link>
      <guid>https://dev.to/ephraimx/how-to-deploy-a-full-stack-application-to-koyeb-using-docker-compose-terraform-and-jenkins-cmj</guid>
      <description>&lt;p&gt;In this article, we will walk you through the process of automating the deployment of a full-stack application on Koyeb using Terraform for infrastructure as code, combined with Jenkins as the continuous integration and continuous deployment (CI/CD) tool. Jenkins offers a flexible and widely adopted platform for orchestrating pipelines, enabling teams to build, test, and deploy applications efficiently.&lt;/p&gt;

&lt;p&gt;Before proceeding, it is recommended to review the &lt;a href="https://dev.to/ephraimx/how-to-deploy-a-full-stack-application-to-koyeb-using-docker-compose-terraform-and-github-actions-oa"&gt;previous article on deploying with Docker Compose and Terraform using GitHub Actions&lt;/a&gt; to understand the Terraform configuration and Docker setup. This article will build upon that foundation by focusing on Jenkins for managing the CI/CD pipeline.&lt;/p&gt;

&lt;p&gt;We will cover how to configure Jenkins to execute Terraform commands that provision and manage your Koyeb infrastructure, ensuring your application environment is consistent and up-to-date. Whether you’re new to Jenkins or Terraform, this guide provides a clear, step-by-step approach to integrating these powerful tools for seamless cloud deployments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;Obtaining Your Koyeb API Token and Setting Up Jenkins Credentials&lt;/li&gt;
&lt;li&gt;Creating the Jenkins Pipeline (Jenkinsfile)&lt;/li&gt;
&lt;li&gt;Pushing Code and Monitoring Deployment&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before proceeding with this guide, ensure you have the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Familiarity with Jenkins pipelines and how to create and manage Jenkins jobs.
If you are new to Jenkins pipelines, please refer to this &lt;a href="https://www.jenkins.io/doc/pipeline/tour/hello-world/" rel="noopener noreferrer"&gt;comprehensive Jenkins pipeline tutorial&lt;/a&gt; for step-by-step guidance.&lt;/li&gt;
&lt;li&gt;Access to a Jenkins server with permission to create pipelines and manage credentials.&lt;/li&gt;
&lt;li&gt;A Koyeb account with a valid API token. This token is necessary to authenticate Jenkins with Koyeb for deployment. Instructions for obtaining this token are provided in the next section.&lt;/li&gt;
&lt;li&gt;The repository containing the Terraform configuration files for deploying the application to Koyeb. If you haven’t set this up yet, see the supporting article on deploying with Terraform and GitHub Actions for detailed configuration.&lt;/li&gt;
&lt;li&gt;A basic understanding of Terraform and Docker Compose is helpful, though the Terraform configuration is covered in the referenced article above.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Obtaining Your Koyeb API Token and Configuring Jenkins Credentials
&lt;/h2&gt;

&lt;p&gt;To enable Jenkins to deploy your application to Koyeb, you need to generate a Koyeb API token and securely store it in Jenkins as a credential.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to Get Your Koyeb API Token
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Log in to your Koyeb dashboard at &lt;a href="https://app.koyeb.com" rel="noopener noreferrer"&gt;https://app.koyeb.com&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Navigate to &lt;strong&gt;Account Settings&lt;/strong&gt; (usually accessible from the user profile menu).&lt;/li&gt;
&lt;li&gt;Select the &lt;strong&gt;API Tokens&lt;/strong&gt; section.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create New Token&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Give your token a descriptive name (e.g., &lt;code&gt;Jenkins Deployment Token&lt;/code&gt;) and set appropriate permissions.&lt;/li&gt;
&lt;li&gt;Generate the token and &lt;strong&gt;copy it immediately&lt;/strong&gt; — you won’t be able to view it again.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Adding the API Token to Jenkins Credentials
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Log in to your Jenkins server.&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Manage Jenkins&lt;/strong&gt; &amp;gt; &lt;strong&gt;Manage Credentials&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Select the appropriate domain (usually &lt;strong&gt;Global&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Add Credentials&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;Kind&lt;/strong&gt;, select &lt;strong&gt;Secret text&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;In the &lt;strong&gt;Secret&lt;/strong&gt; field, paste your copied Koyeb API token.&lt;/li&gt;
&lt;li&gt;Give it an &lt;strong&gt;ID&lt;/strong&gt; (for example, &lt;code&gt;KOYEB_API_TOKEN&lt;/code&gt;) and optionally a description.&lt;/li&gt;
&lt;li&gt;Save the credential.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Creating the Jenkinsfile for Terraform Deployment
&lt;/h2&gt;

&lt;p&gt;To automate the deployment of your full-stack application to Koyeb using Terraform and Jenkins, you need to create a &lt;code&gt;Jenkinsfile&lt;/code&gt; in the root directory of your repository.&lt;/p&gt;

&lt;h3&gt;
  
  
  Steps:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;In your project’s root directory, create a file named &lt;code&gt;Jenkinsfile&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Paste the following pipeline script into the &lt;code&gt;Jenkinsfile&lt;/code&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;pipeline&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt;

  &lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;KOYEB_TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'KOYEB_API_TOKEN'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;stages&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Install Terraform &amp;amp; Deploy'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'terraform'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'''
            #!/bin/bash
            set -e
            if [ ! -f terraform ]; then
              echo "Downloading Terraform binary..."
              curl -fsSL https://releases.hashicorp.com/terraform/1.11.4/terraform_1.11.4_linux_amd64.zip -o terraform.zip
              unzip terraform.zip
              chmod +x terraform
              rm terraform.zip
            fi

            ./terraform version
            ./terraform init
            ./terraform fmt
            ./terraform validate
            ./terraform plan
            ./terraform apply --auto-approve
          '''&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;agent any:&lt;/strong&gt; Runs the pipeline on any available Jenkins agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;environment:&lt;/strong&gt; The &lt;code&gt;KOYEB_TOKEN&lt;/code&gt; environment variable is set using the Jenkins credential ID &lt;code&gt;KOYEB_API_TOKEN&lt;/code&gt;. Make sure this matches the ID you configured in Jenkins credentials.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;stage ‘Install Terraform &amp;amp; Deploy’:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;The script moves into the &lt;code&gt;terraform&lt;/code&gt; directory where your Terraform configuration files reside.&lt;/li&gt;
&lt;li&gt;Checks if Terraform binary exists; if not, downloads and extracts Terraform 1.11.4.&lt;/li&gt;
&lt;li&gt;Runs Terraform commands in sequence:&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform version&lt;/code&gt; to confirm the version,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform init&lt;/code&gt; to initialize the working directory,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform fmt&lt;/code&gt; to check formatting,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform validate&lt;/code&gt; to validate the configuration,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform plan&lt;/code&gt; to preview changes,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform apply&lt;/code&gt; to apply infrastructure changes automatically.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pushing Code and Monitoring Jenkins Pipeline Deployment
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Pushing Your Code to Prepare for Deployment
&lt;/h3&gt;

&lt;p&gt;After creating and committing the &lt;code&gt;Jenkinsfile&lt;/code&gt; along with your Terraform configuration files in the &lt;code&gt;terraform&lt;/code&gt; directory, push the changes to your remote Git 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 add Jenkinsfile terraform/
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Add Jenkins pipeline for Terraform deployment to Koyeb"&lt;/span&gt;
git push origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Depending on your Jenkins setup, pushing your code may &lt;strong&gt;not&lt;/strong&gt; automatically trigger the pipeline. You might need to manually start the build by navigating to your Jenkins job and clicking &lt;strong&gt;Build Now&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Monitoring the Jenkins Pipeline
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Access Jenkins Dashboard:&lt;/strong&gt;
Log in to your Jenkins instance and go to the dashboard.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Locate Your Pipeline Job:&lt;/strong&gt;
Find the pipeline job configured for this repository (e.g., &lt;code&gt;glowberry-terraform-deploy&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Start the Build (If Needed):&lt;/strong&gt;
If the pipeline doesn’t start automatically, click &lt;strong&gt;Build Now&lt;/strong&gt; to initiate the deployment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;View Build History:&lt;/strong&gt;
Once started, your build will appear in the build history list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open Build Details:&lt;/strong&gt;
Click on the latest build number for detailed information.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Follow Console Output:&lt;/strong&gt;
Click &lt;strong&gt;Console Output&lt;/strong&gt; to monitor the pipeline logs in real time, including:

&lt;ul&gt;
&lt;li&gt;Terraform download and setup&lt;/li&gt;
&lt;li&gt;Terraform init, fmt, validate&lt;/li&gt;
&lt;li&gt;Terraform plan and apply&lt;/li&gt;
&lt;li&gt;Deployment progress and errors (if any)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confirm Deployment Success:&lt;/strong&gt;
The build is successful if all steps complete without error, and Terraform apply confirms resource creation or updates.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;In this article, you learned how to automate the deployment of a full-stack application to Koyeb using Terraform within a Jenkins pipeline. By integrating Infrastructure as Code with your CI/CD process, you achieve consistent, repeatable, and manageable deployments.&lt;/p&gt;

&lt;p&gt;With the provided &lt;code&gt;Jenkinsfile&lt;/code&gt; and Terraform configuration, you can efficiently provision and update your cloud resources while maintaining full control through Jenkins. This approach enhances deployment reliability and streamlines your development workflow.&lt;/p&gt;

&lt;p&gt;For a deeper understanding of the Terraform configuration used here, refer to the supporting article linked in the prerequisites section.&lt;/p&gt;

&lt;p&gt;Feel free to explore additional enhancements such as pipeline notifications, environment-specific deployments, or secret management integrations to optimize your deployment process further.&lt;/p&gt;

&lt;p&gt;Found this guide helpful? Follow &lt;a href="https://github.com/EphraimX" rel="noopener noreferrer"&gt;EphraimX&lt;/a&gt; for more hands-on DevOps walkthroughs. You can also connect with me on &lt;a href="https://www.linkedin.com/in/oghenefejiro-esosuota" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; or explore more of my work on my &lt;a href="https://ephraim-x-github-io.vercel.app/" rel="noopener noreferrer"&gt;portfolio&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>terraform</category>
      <category>jenkins</category>
      <category>docker</category>
    </item>
    <item>
      <title>How to Deploy a Full Stack App to Koyeb Using Docker Compose and Jenkins</title>
      <dc:creator>EphraimX</dc:creator>
      <pubDate>Fri, 20 Jun 2025 13:37:40 +0000</pubDate>
      <link>https://dev.to/ephraimx/how-to-deploy-a-full-stack-app-to-koyeb-using-docker-compose-and-jenkins-19a7</link>
      <guid>https://dev.to/ephraimx/how-to-deploy-a-full-stack-app-to-koyeb-using-docker-compose-and-jenkins-19a7</guid>
      <description>&lt;p&gt;Jenkins remains one of the most powerful and flexible tools in the CI/CD space—widely adopted for its extensibility and tight control over build pipelines. In this article, we’ll explore how to deploy a full-stack Docker Compose application to Koyeb using a Jenkins pipeline.&lt;/p&gt;

&lt;p&gt;Unlike managed CI/CD platforms like GitHub Actions or GitLab CI/CD, Jenkins gives you complete control over the environment and pipeline steps. This makes it a great choice when you need custom configurations, self-hosted agents, or integration with complex systems.&lt;/p&gt;

&lt;p&gt;We’ll skip the broader details of the application setup itself. If you haven’t already, you should &lt;a href="https://dev.to/ephraimx/how-to-deploy-a-full-stack-app-to-koyeb-using-docker-compose-and-github-actions-2oj"&gt;read the foundational GitHub Actions article&lt;/a&gt; for insights into the Dockerfiles, Docker Compose configuration, and the overall structure of the Glowberry Global Tax Structure Simulator app. This guide will focus entirely on the Jenkins pipeline configuration and how to automate the deployment to Koyeb from there.&lt;/p&gt;

&lt;p&gt;Let’s get started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;Configuring Koyeb API Token in Jenkins&lt;/li&gt;
&lt;li&gt;Jenkins Pipeline Setup&lt;/li&gt;
&lt;li&gt;Understanding the Deployment Steps&lt;/li&gt;
&lt;li&gt;Executing the Pipeline&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before proceeding with this Jenkins-specific guide, ensure the following prerequisites are met:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Read the Supporting Article&lt;/strong&gt;: This article builds on concepts covered in &lt;a href="https://dev.to/ephraimx/how-to-deploy-a-full-stack-app-to-koyeb-using-docker-compose-and-github-actions-2oj"&gt;How to Deploy a Full Stack App to Koyeb Using Docker Compose and GitHub Actions&lt;/a&gt;. That guide explains the structure of the application, Docker setup, and how Docker Compose and &lt;code&gt;Dockerfile.koyeb&lt;/code&gt; drive the deployment. Please review it first to understand the foundational components.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jenkins Installed and Running&lt;/strong&gt;: You should have Jenkins installed and accessible on your system or through a server (such as localhost or a cloud VM).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jenkins User with Required Permissions&lt;/strong&gt;: Ensure your Jenkins user has the necessary permissions to manage credentials and execute pipelines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Install Required Jenkins Plugins&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pipeline&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub Integration&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Git Plugin&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Docker Pipeline&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment Injector&lt;/strong&gt; (for managing environment variables)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Koyeb Account&lt;/strong&gt;: If you haven’t already, create a Koyeb account. This guide assumes you have access to it and can generate an API token.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Koyeb API Token&lt;/strong&gt;: You’ll need a valid Koyeb API token for deployment. We'll walk through storing it securely in Jenkins shortly.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;GitHub Repository&lt;/strong&gt;: Your application should be stored in a GitHub repository accessible to Jenkins.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Absolutely. Here’s a concise section explaining how to retrieve your Koyeb API key before using it in Jenkins:&lt;/p&gt;

&lt;h2&gt;
  
  
  Retrieving Your Koyeb API Token
&lt;/h2&gt;

&lt;p&gt;To interact with Koyeb from CI/CD tools like Jenkins, you'll need a &lt;strong&gt;Koyeb API token&lt;/strong&gt;. Here's how to get it:&lt;/p&gt;

&lt;h3&gt;
  
  
  How to Generate a Koyeb API Token
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Log In to Your Koyeb Account&lt;/strong&gt;
Visit &lt;a href="https://app.koyeb.com" rel="noopener noreferrer"&gt;https://app.koyeb.com&lt;/a&gt; and sign in&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go to Account Settings&lt;/strong&gt;
Click on your profile icon at the top-right corner and select &lt;strong&gt;Account Settings&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Navigate to the API Tab&lt;/strong&gt;
In the settings sidebar, click on &lt;strong&gt;API&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generate a New API Token&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Click the &lt;strong&gt;Generate API Token&lt;/strong&gt; button.&lt;/li&gt;
&lt;li&gt;Give your token a descriptive name (e.g., &lt;code&gt;jenkins-deployment-token&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Choose the appropriate &lt;strong&gt;scope&lt;/strong&gt; (typically full access for CI/CD).&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create Token&lt;/strong&gt; and &lt;strong&gt;copy&lt;/strong&gt; the token immediately—this is the only time it will be fully visible.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once retrieved, you can securely store the token in Jenkins as described in the next section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring Koyeb API Token in Jenkins
&lt;/h2&gt;

&lt;p&gt;To allow Jenkins to securely deploy your application to Koyeb, you'll need to store your Koyeb API token as a &lt;strong&gt;Secret Text credential&lt;/strong&gt; in Jenkins. Follow these steps carefully:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step-by-Step: Store Koyeb API Token in Jenkins
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Access Jenkins Dashboard&lt;/strong&gt;
Log into your Jenkins instance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Navigate to Credentials&lt;/strong&gt;
From the sidebar, go to:
&lt;strong&gt;Manage Jenkins&lt;/strong&gt; → &lt;strong&gt;Credentials&lt;/strong&gt; → &lt;strong&gt;(select a domain or Global)&lt;/strong&gt; → &lt;strong&gt;Add Credentials&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Select Credential Type&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Kind&lt;/strong&gt;: Choose &lt;strong&gt;Secret text&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secret&lt;/strong&gt;: Paste your &lt;strong&gt;Koyeb API token&lt;/strong&gt; here&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ID&lt;/strong&gt;: Use a simple identifier like &lt;code&gt;KOYEB_API_TOKEN&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Description&lt;/strong&gt;: Optional, e.g., “Koyeb token for CI/CD deployment”&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Save&lt;/strong&gt;
Click &lt;strong&gt;OK&lt;/strong&gt; to save the credential.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Jenkins will now securely reference this token using the ID &lt;code&gt;KOYEB_API_TOKEN&lt;/code&gt; in your pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  New to Jenkins Pipelines?
&lt;/h3&gt;

&lt;p&gt;If you're unfamiliar with how to create or run a Jenkins pipeline, I’ve written a companion article to guide you through the process. It covers creating a Jenkinsfile, setting up a pipeline job, and connecting it to a GitHub repository. &lt;a href="https://www.jenkins.io/doc/pipeline/tour/hello-world/" rel="noopener noreferrer"&gt;Refer to the Jenkins Pipeline Setup Guide&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the &lt;code&gt;Jenkinsfile&lt;/code&gt; for Your Pipeline
&lt;/h2&gt;

&lt;p&gt;With your Koyeb API token securely stored in Jenkins credentials (as &lt;code&gt;KOYEB_API_TOKEN&lt;/code&gt;), you’re ready to define your CI/CD pipeline.&lt;/p&gt;

&lt;p&gt;Follow these steps:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Create the &lt;code&gt;Jenkinsfile&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;In the &lt;strong&gt;root directory&lt;/strong&gt; of your project repository, create a file named &lt;code&gt;Jenkinsfile&lt;/code&gt; with no extension.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Paste the Following Code into the &lt;code&gt;Jenkinsfile&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;pipeline&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

  &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt;

  &lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;KOYEB_API_TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'KOYEB_API_TOKEN'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;stages&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Koyeb Setup and Deploy Job'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// sh 'apt install curl' // Uncomment if curl is not installed on your Jenkins agent&lt;/span&gt;
        &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'curl -fsSL https://raw.githubusercontent.com/koyeb/koyeb-cli/master/install.sh | sh'&lt;/span&gt;
        &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'''
          export PATH="/var/jenkins_home/.koyeb/bin:$PATH"
          export KOYEB_TOKEN=$KOYEB_API_TOKEN
          koyeb app create glowberry-tax-structure-simulator-glabcicd-docker-compose-koyeb
          koyeb service create glowberry-tax-structure-simulator-glabcicd-docker-compose-koyeb \
            --app glowberry-tax-structure-simulator-glabcicd-docker-compose-koyeb \
            --git github.com/EphraimX/glowberry-global-tax-structure-simulator-gha-docker-compose-koyeb \
            --instance-type free \
            --git-builder docker \
            --git-docker-dockerfile Dockerfile.koyeb \
            --port 3000:http \
            --route /:3000 \
            --privileged
        '''&lt;/span&gt;
      &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;pipeline&lt;/code&gt;&lt;/strong&gt;: Declares a declarative Jenkins pipeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;agent any&lt;/code&gt;&lt;/strong&gt;: Runs the pipeline on any available agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;environment&lt;/code&gt;&lt;/strong&gt;: Injects the stored Koyeb API token from Jenkins credentials into the environment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;stage('Koyeb Setup and Deploy Job')&lt;/code&gt;&lt;/strong&gt;: This stage does the heavy lifting:

&lt;ul&gt;
&lt;li&gt;Installs the Koyeb CLI via curl.&lt;/li&gt;
&lt;li&gt;Updates the &lt;code&gt;PATH&lt;/code&gt; so the CLI is accessible.&lt;/li&gt;
&lt;li&gt;Sets the &lt;code&gt;KOYEB_TOKEN&lt;/code&gt; environment variable.&lt;/li&gt;
&lt;li&gt;Uses the Koyeb CLI to:

&lt;ul&gt;
&lt;li&gt;Create the app (if it doesn’t already exist).&lt;/li&gt;
&lt;li&gt;Deploy the service using:&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Dockerfile.koyeb&lt;/code&gt; as the deployment entry point.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;docker&lt;/code&gt; Git builder.&lt;/li&gt;
&lt;li&gt;Port &lt;code&gt;3000&lt;/code&gt; exposed publicly.&lt;/li&gt;
&lt;li&gt;Route all root requests to port &lt;code&gt;3000&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;--privileged&lt;/code&gt; flag (important for some internal service behavior).&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;If &lt;code&gt;curl&lt;/code&gt; is not installed on your Jenkins agent, simply uncomment the &lt;code&gt;apt install curl&lt;/code&gt; line near the top.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Deploying to Koyeb with Jenkins
&lt;/h2&gt;

&lt;p&gt;Once your &lt;code&gt;Jenkinsfile&lt;/code&gt; is in place and committed to your repository, deploying your application becomes a matter of triggering a Jenkins pipeline build. Here's how to proceed:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Push Your Code to the Repository
&lt;/h3&gt;

&lt;p&gt;Commit and push your updated project — including the new &lt;code&gt;Jenkinsfile&lt;/code&gt; — to the main branch (or the branch your Jenkins job is configured to track).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add Jenkinsfile
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Add Jenkins pipeline for Koyeb deployment"&lt;/span&gt;
git push origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Make sure your Jenkins job is configured to monitor the correct repository and branch.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  2. Trigger the Jenkins Job
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;If your Jenkins project is configured for &lt;strong&gt;polling SCM&lt;/strong&gt; or &lt;strong&gt;webhook-based triggers&lt;/strong&gt;, Jenkins will automatically detect the new commit and run the job.&lt;/li&gt;
&lt;li&gt;If not, you can &lt;strong&gt;manually trigger the job&lt;/strong&gt; from the Jenkins dashboard by clicking &lt;strong&gt;"Build Now"&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Monitor the Build
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Navigate to your Jenkins dashboard.&lt;/li&gt;
&lt;li&gt;Click on your project, then click on the build under the &lt;strong&gt;"Build History"&lt;/strong&gt; section.&lt;/li&gt;
&lt;li&gt;Use the &lt;strong&gt;"Console Output"&lt;/strong&gt; tab to monitor the step-by-step progress of your pipeline.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If everything is configured correctly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The pipeline will install the Koyeb CLI.&lt;/li&gt;
&lt;li&gt;Authenticate with the Koyeb API using your secret token.&lt;/li&gt;
&lt;li&gt;Create the app and service.&lt;/li&gt;
&lt;li&gt;Deploy your application using the &lt;code&gt;Dockerfile.koyeb&lt;/code&gt; and the &lt;code&gt;docker-compose.yml&lt;/code&gt; file.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Verify the Deployment
&lt;/h3&gt;

&lt;p&gt;After a successful build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Visit your &lt;strong&gt;Koyeb dashboard&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Locate the app and service by name.&lt;/li&gt;
&lt;li&gt;Open the service URL to access your deployed frontend application running on port &lt;code&gt;3000&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Integrating Jenkins into your CI/CD workflow to deploy a full-stack Docker Compose application to Koyeb gives you fine-grained control over your build and release processes. By leveraging the &lt;code&gt;Dockerfile.koyeb&lt;/code&gt;, automating deployment through the Koyeb CLI, and managing secrets securely within Jenkins, you can achieve repeatable and consistent deployments across environments.&lt;/p&gt;

&lt;p&gt;If you're already maintaining infrastructure with Jenkins, this method fits seamlessly into your pipeline architecture. For developers transitioning from other CI/CD systems, Jenkins remains a powerful and flexible alternative, especially when paired with a platform like Koyeb that abstracts infrastructure complexity.&lt;/p&gt;

&lt;p&gt;With this foundation in place, you can now iterate faster, maintain confidence in your deployments, and explore more advanced Jenkins features such as parallel builds, test automation, and staged rollouts.&lt;/p&gt;

&lt;p&gt;Found this guide helpful? Follow &lt;a href="https://github.com/EphraimX" rel="noopener noreferrer"&gt;EphraimX&lt;/a&gt; for more hands-on DevOps walkthroughs. You can also connect with me on &lt;a href="https://www.linkedin.com/in/oghenefejiro-esosuota" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; or explore more of my work on my &lt;a href="https://ephraim-x-github-io.vercel.app/" rel="noopener noreferrer"&gt;portfolio&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>jenkins</category>
      <category>devops</category>
      <category>docker</category>
      <category>koyeb</category>
    </item>
    <item>
      <title>How to Deploy a Full-Stack Application to Koyeb Using Docker Compose, Pulumi and GitLab CI/CD</title>
      <dc:creator>EphraimX</dc:creator>
      <pubDate>Mon, 16 Jun 2025 13:28:07 +0000</pubDate>
      <link>https://dev.to/ephraimx/how-to-deploy-a-full-stack-application-to-koyeb-using-docker-compose-pulumi-and-gitlab-cicd-2nb3</link>
      <guid>https://dev.to/ephraimx/how-to-deploy-a-full-stack-application-to-koyeb-using-docker-compose-pulumi-and-gitlab-cicd-2nb3</guid>
      <description>&lt;p&gt;This article demonstrates how to deploy your full-stack application to Koyeb by leveraging Pulumi infrastructure-as-code alongside GitLab CI/CD pipelines.&lt;/p&gt;

&lt;p&gt;Building on the foundational Pulumi setup detailed in the &lt;a href="https://dev.to/ephraimx/how-to-deploy-a-docker-compose-app-to-koyeb-using-pulumi-and-github-actions-517b"&gt;previous GitHub Actions Pulumi article&lt;/a&gt;, this guide focuses on integrating Pulumi deployment workflows within GitLab CI/CD. We won't dive deep into Pulumi code here, so if you haven't set up your Pulumi infrastructure yet, please refer to the earlier article for full context.&lt;/p&gt;

&lt;p&gt;By the end of this tutorial, you'll have a clear understanding of configuring GitLab pipelines to automate infrastructure provisioning and application deployment using Pulumi on Koyeb.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;Setting Up GitLab CI/CD&lt;/li&gt;
&lt;li&gt;Pushing Code and Triggering Deployment&lt;/li&gt;
&lt;li&gt;Monitoring Deployment Progress&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Complete the foundational Pulumi setup as detailed in the &lt;a href="https://dev.to/ephraimx/how-to-deploy-a-docker-compose-app-to-koyeb-using-pulumi-and-github-actions-517b"&gt;Deploying a Full Stack Application with Pulumi on Koyeb Using GitHub Actions&lt;/a&gt; article.&lt;/li&gt;
&lt;li&gt;Ensure your Pulumi project directory contains these files:
&lt;code&gt;Pulumi.yaml&lt;/code&gt;, &lt;code&gt;__main__.py&lt;/code&gt;, &lt;code&gt;requirements.txt&lt;/code&gt;, and &lt;code&gt;.gitignore&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Have a GitLab repository with your Pulumi project committed.&lt;/li&gt;
&lt;li&gt;Enable GitLab CI/CD on your repository.&lt;/li&gt;
&lt;li&gt;Add the following environment variables in your GitLab project settings under &lt;strong&gt;Settings &amp;gt; CI/CD &amp;gt; Variables&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;KOYEB_TOKEN&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PULUMI_ACCESS_TOKEN&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Basic understanding of GitLab pipelines and YAML configuration.&lt;/li&gt;

&lt;li&gt;Confirm your Pulumi stack is initialized and properly configured as described in the foundational article.&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setting Up GitLab CI/CD
&lt;/h2&gt;

&lt;p&gt;To automate your Pulumi deployment using GitLab CI/CD, start by creating a &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; file at the root of your repository. This file defines your pipeline and deployment process.&lt;/p&gt;

&lt;p&gt;Before proceeding, ensure you have added the following &lt;strong&gt;environment variables&lt;/strong&gt; to your GitLab project's &lt;strong&gt;CI/CD settings&lt;/strong&gt; under &lt;strong&gt;Settings &amp;gt; CI/CD &amp;gt; Variables&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;KOYEB_API_TOKEN&lt;/code&gt;- Your Koyeb API token.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PULUMI_ACCESS_TOKEN&lt;/code&gt;- Your Pulumi access token.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, create &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pulumi_setup_and_deploy_koyeb&lt;/span&gt;

&lt;span class="na"&gt;.standard-rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH&lt;/span&gt;

&lt;span class="na"&gt;setup-and-deploy-pulumi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pulumi_setup_and_deploy_koyeb&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;python:3.11-slim&lt;/span&gt;
  &lt;span class="na"&gt;before_script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cd pulumi-koyeb&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;apt-get update&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;apt-get install -y curl&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;curl -fsSL https://get.pulumi.com | sh&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mkdir -p ~/.pulumi/plugins/resource-koyeb-v0.1.11&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;curl -L https://github.com/koyeb/pulumi-koyeb/releases/download/v0.1.11/pulumi-resource-koyeb-v0.1.11-linux-amd64.tar.gz | tar -xz -C ~/.pulumi/plugins/resource-koyeb-v0.1.11&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export PATH=$HOME/.pulumi/bin:$PATH&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pulumi login&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pulumi stack select glowberry-dev-gha-stack || pulumi stack init glowberry-dev-gha-stack&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pulumi preview&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pulumi up -y&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;KOYEB_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$KOYEB_API_TOKEN&lt;/span&gt;
    &lt;span class="na"&gt;PULUMI_ACCESS_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$PULUMI_ACCESS_TOKEN&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation of the Pipeline
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stages&lt;/strong&gt;: Defines a single stage called &lt;code&gt;pulumi_setup_and_deploy_koyeb&lt;/code&gt; which runs the deployment job.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rules&lt;/strong&gt;: The &lt;code&gt;.standard-rules&lt;/code&gt; ensure the pipeline runs only on the default branch (usually &lt;code&gt;main&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Job &lt;code&gt;setup-and-deploy-pulumi&lt;/code&gt;&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;image&lt;/code&gt;&lt;/strong&gt;: Uses the official slim Python 3.11 Docker image as the runtime environment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;before_script&lt;/code&gt;&lt;/strong&gt;:&lt;/li&gt;
&lt;li&gt;Changes directory to &lt;code&gt;pulumi-koyeb&lt;/code&gt; where your Pulumi project files are located.&lt;/li&gt;
&lt;li&gt;Updates the package lists and installs &lt;code&gt;curl&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Installs the Pulumi CLI by downloading and running its installation script.&lt;/li&gt;
&lt;li&gt;Downloads and installs the Koyeb Pulumi provider plugin into Pulumiâ€™s plugin directory.&lt;/li&gt;
&lt;li&gt;Adds Pulumi CLI to the PATH environment variable to make the &lt;code&gt;pulumi&lt;/code&gt; command available.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;script&lt;/code&gt;&lt;/strong&gt;:&lt;/li&gt;
&lt;li&gt;Runs &lt;code&gt;pulumi login&lt;/code&gt; to authenticate with the Pulumi service.&lt;/li&gt;
&lt;li&gt;Selects the Pulumi stack named &lt;code&gt;glowberry-dev-gha-stack&lt;/code&gt; or creates it if it doesn't exist.&lt;/li&gt;
&lt;li&gt;Runs &lt;code&gt;pulumi preview&lt;/code&gt; to display the planned infrastructure changes.&lt;/li&gt;
&lt;li&gt;Executes &lt;code&gt;pulumi up -y&lt;/code&gt; to apply the changes automatically, deploying your app to Koyeb.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;variables&lt;/code&gt;&lt;/strong&gt;:&lt;/li&gt;
&lt;li&gt;Passes the necessary &lt;code&gt;KOYEB_TOKEN&lt;/code&gt; and &lt;code&gt;PULUMI_ACCESS_TOKEN&lt;/code&gt; as environment variables inside the job, linked to your GitLab project's stored secrets.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;This pipeline fully automates the Pulumi infrastructure deployment process to Koyeb each time you push changes to the default branch, making your CI/CD flow smooth and reliable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pushing Code and Monitoring Your Pulumi Deployment
&lt;/h2&gt;

&lt;p&gt;After setting up your &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; pipeline file, push your changes to the default branch (usually &lt;code&gt;main&lt;/code&gt;) to trigger the GitLab CI/CD pipeline and start your Pulumi deployment on Koyeb.&lt;/p&gt;

&lt;p&gt;Run the following commands in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Add Pulumi deployment pipeline for Koyeb on GitLab"&lt;/span&gt;
git push origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This push will automatically start the CI/CD pipeline you configured, which will execute the Pulumi deployment job.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monitoring Deployment Progress in GitLab
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Navigate to Your GitLab Project:&lt;/strong&gt;
Open your GitLab repository in a browser.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go to the CI/CD &amp;gt; Pipelines Section:&lt;/strong&gt;
In the left sidebar, click on &lt;strong&gt;CI/CD&lt;/strong&gt; and then &lt;strong&gt;Pipelines&lt;/strong&gt; to see the list of pipeline runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Find the Latest Pipeline Run:&lt;/strong&gt;
The pipeline triggered by your recent push should be at the top of the list. Click on the pipeline status (e.g., running, passed, failed) to view its details.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;View Job Logs:&lt;/strong&gt;
Click on the job named &lt;code&gt;setup-and-deploy-pulumi&lt;/code&gt; to see real-time logs of the deployment process. You can monitor:

&lt;ul&gt;
&lt;li&gt;The Pulumi CLI installation steps.&lt;/li&gt;
&lt;li&gt;Authentication with Pulumi and Koyeb.&lt;/li&gt;
&lt;li&gt;Pulumi preview output showing the planned infrastructure changes.&lt;/li&gt;
&lt;li&gt;The progress and completion of &lt;code&gt;pulumi up&lt;/code&gt; which applies your infrastructure updates.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confirm Successful Deployment:&lt;/strong&gt;
A successful pipeline run indicates your application and infrastructure have been deployed or updated on Koyeb via Pulumi.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This monitoring approach helps you verify your deployment's status and troubleshoot any issues early by examining the job logs in detail.&lt;/p&gt;

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

&lt;p&gt;In this guide, you successfully set up a GitLab CI/CD pipeline to deploy your Glowberry Tax Simulator infrastructure on Koyeb using Pulumi. By leveraging Pulumi's powerful infrastructure-as-code capabilities combined with GitLab's robust CI/CD platform, you can automate deployments efficiently and reliably.&lt;/p&gt;

&lt;p&gt;For a deeper understanding of the Pulumi infrastructure code and setup, refer to our foundational Pulumi article. With this pipeline in place, you are well-equipped to manage and scale your cloud infrastructure as your application grows.&lt;/p&gt;

&lt;p&gt;Found this guide helpful? Follow &lt;a href="https://github.com/EphraimX" rel="noopener noreferrer"&gt;EphraimX&lt;/a&gt; for more hands-on DevOps walkthroughs. You can also connect with me on &lt;a href="https://www.linkedin.com/in/oghenefejiro-esosuota" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; or explore more of my work on my &lt;a href="https://ephraim-x-github-io.vercel.app/" rel="noopener noreferrer"&gt;portfolio&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>docker</category>
      <category>pulumi</category>
      <category>gitlab</category>
    </item>
    <item>
      <title>How to Deploy a Full Stack Application to Koyeb Using Docker Compose, Terraform, and GitLab CI/CD</title>
      <dc:creator>EphraimX</dc:creator>
      <pubDate>Fri, 13 Jun 2025 13:20:22 +0000</pubDate>
      <link>https://dev.to/ephraimx/how-to-deploy-a-full-stack-application-to-koyeb-using-docker-compose-terraform-and-gitlab-cicd-4coa</link>
      <guid>https://dev.to/ephraimx/how-to-deploy-a-full-stack-application-to-koyeb-using-docker-compose-terraform-and-gitlab-cicd-4coa</guid>
      <description>&lt;p&gt;Deploying a full stack application efficiently requires combining reliable infrastructure management with automated deployment pipelines. In this article, we’ll walk through how to deploy a Docker Compose-based full stack app to Koyeb using Terraform for infrastructure as code and GitLab CI/CD for continuous integration and deployment.&lt;/p&gt;

&lt;p&gt;If you haven’t already, please refer to our detailed &lt;a href="https://dev.to/ephraimx/how-to-deploy-a-full-stack-application-to-koyeb-using-docker-compose-terraform-and-github-actions-oa"&gt;guide&lt;/a&gt; on deploying with Docker Compose, Terraform, and GitHub Actions, which covers the Terraform configuration and Docker setup (in a linked article) in depth. This article focuses specifically on adapting that setup to GitLab’s CI/CD platform, so you can leverage GitLab pipelines to automate your deployments seamlessly.&lt;/p&gt;

&lt;p&gt;By the end, you’ll have a clear understanding of configuring GitLab CI/CD to work hand-in-hand with Terraform to deploy and manage your application on Koyeb, enabling a modern, scalable, and maintainable deployment workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;Setting Up Koyeb API Token in GitLab CI/CD&lt;/li&gt;
&lt;li&gt;GitLab CI/CD Pipeline Configuration&lt;/li&gt;
&lt;li&gt;Deploying to Koyeb with GitLab CI/CD&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before proceeding, ensure you have completed the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Familiarity with the Docker Compose setup:&lt;/strong&gt; This article builds upon the Docker Compose configuration explained in &lt;a href="https://dev.to/ephraimx/how-to-deploy-a-full-stack-app-to-koyeb-using-docker-compose-and-github-actions-2oj"&gt;How to Deploy a Full Stack App to Koyeb Using Docker Compose and GitHub Actions&lt;/a&gt;. Please review that article if you haven’t already.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Koyeb account:&lt;/strong&gt; Sign up at &lt;a href="https://www.koyeb.com" rel="noopener noreferrer"&gt;https://www.koyeb.com&lt;/a&gt; and verify your account.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitLab repository access:&lt;/strong&gt; Ensure your full stack application code is hosted on a GitLab repository.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Basic knowledge of GitLab CI/CD:&lt;/strong&gt; If you’re new to GitLab CI/CD, familiarize yourself with GitLab’s pipeline concepts and how to add CI/CD variables. The official documentation is a good starting point: &lt;a href="https://docs.gitlab.com/ee/ci/" rel="noopener noreferrer"&gt;GitLab CI/CD Basics&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Koyeb API Token:&lt;/strong&gt;
You will need your Koyeb API token to allow GitLab to deploy your app. Instructions on obtaining and adding this token to your GitLab project’s CI/CD variables will be covered in the next section.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Obtaining Your Koyeb API Token and Adding It to GitLab
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Get your Koyeb API token:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Log in to your Koyeb dashboard.&lt;/li&gt;
&lt;li&gt;Navigate to &lt;strong&gt;Account Settings &amp;gt; API Tokens&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Generate a new token if you don’t have one already.&lt;/li&gt;
&lt;li&gt;Copy the token securely.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add the token to GitLab:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Go to your GitLab project.&lt;/li&gt;
&lt;li&gt;Navigate to &lt;strong&gt;Settings &amp;gt; CI/CD &amp;gt; Variables&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Add Variable&lt;/strong&gt; and enter:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Key:&lt;/strong&gt; &lt;code&gt;KOYEB_API_TOKEN&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Value:&lt;/strong&gt; &lt;em&gt;(paste your Koyeb API token)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Enable &lt;strong&gt;Masked&lt;/strong&gt; and &lt;strong&gt;Protected&lt;/strong&gt; options for security.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Save the variable.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  GitLab CI/CD Configuration Explained
&lt;/h2&gt;

&lt;p&gt;To get started with GitLab CI/CD, create a file named &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; in the root directory of your repository. This file will define the pipeline stages and the necessary steps to build and deploy your application using Terraform on Koyeb. Paste the following configuration into the file to automate the deployment process.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;terraform_build_and_deploy&lt;/span&gt;

&lt;span class="na"&gt;.standard-rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH&lt;/span&gt;

&lt;span class="na"&gt;koyeb-deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform_build_and_deploy&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;registry.gitlab.com/gitlab-org/terraform-images/stable:latest&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cd terraform&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;gitlab-terraform init&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;gitlab-terraform fmt&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;gitlab-terraform validate&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;gitlab-terraform plan&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;gitlab-terraform apply&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;KOYEB_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$KOYEB_API_TOKEN&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Breakdown
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;stages:&lt;/strong&gt;
Defines the pipeline stages; here, there’s a single stage called &lt;code&gt;terraform_build_and_deploy&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;.standard-rules:&lt;/strong&gt;
This reusable rule ensures the pipeline runs only when the commit is pushed to the default branch (usually &lt;code&gt;main&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;koyeb-deploy job:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;stage:&lt;/strong&gt; Specifies which stage this job belongs to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;image:&lt;/strong&gt; Uses an official Terraform Docker image optimized for GitLab CI to run the Terraform commands.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;script:&lt;/strong&gt; Runs the following Terraform commands inside the &lt;code&gt;terraform&lt;/code&gt; directory:&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gitlab-terraform init&lt;/code&gt;: Initializes Terraform.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gitlab-terraform fmt&lt;/code&gt;: Checks Terraform code formatting.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gitlab-terraform validate&lt;/code&gt;: Validates Terraform configuration files.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gitlab-terraform plan&lt;/code&gt;: Creates and shows an execution plan.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gitlab-terraform apply&lt;/code&gt;: Applies the Terraform plan automatically, deploying your infrastructure on Koyeb.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;variables:&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;KOYEB_TOKEN&lt;/code&gt;: Passes the Koyeb API token securely from the GitLab CI variable &lt;code&gt;KOYEB_API_TOKEN&lt;/code&gt; to authenticate Terraform with Koyeb.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pushing Code and Monitoring Your Terraform Deployment
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Triggering Deployment by Pushing Code
&lt;/h3&gt;

&lt;p&gt;After committing and pushing your updated Terraform configuration along with the &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; pipeline file to the default branch (usually &lt;code&gt;main&lt;/code&gt;), GitLab CI/CD will automatically trigger the deployment pipeline.&lt;/p&gt;

&lt;p&gt;To push your changes, use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Add Terraform deployment pipeline for Koyeb"&lt;/span&gt;
git push origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This push initiates the CI/CD pipeline, which runs the configured jobs to deploy or update your application on Koyeb.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monitoring the Deployment Process
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Navigate to Your GitLab Project:&lt;/strong&gt;
Open your project on the GitLab web interface.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go to the Build Pipelines Section:&lt;/strong&gt;
Click on &lt;strong&gt;Build&lt;/strong&gt; in the left sidebar, then select &lt;strong&gt;Pipelines&lt;/strong&gt;. This shows all pipeline runs for your project.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Find the Latest Pipeline Run:&lt;/strong&gt;
Locate the most recent pipeline triggered by your push to the &lt;code&gt;main&lt;/code&gt; branch, and click on its status to view details.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review Job Logs:&lt;/strong&gt;
Inside the pipeline view, click on the &lt;code&gt;koyeb-deploy&lt;/code&gt; job to see step-by-step logs. Look for:

&lt;ul&gt;
&lt;li&gt;Successful checkout of your code repository.&lt;/li&gt;
&lt;li&gt;Terraform initialization and formatting checks completing without errors.&lt;/li&gt;
&lt;li&gt;Validation of Terraform configuration.&lt;/li&gt;
&lt;li&gt;Execution of the plan showing infrastructure changes.&lt;/li&gt;
&lt;li&gt;Application of the changes with &lt;code&gt;terraform apply&lt;/code&gt; completing successfully, confirming your app is deployed or updated on Koyeb.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;Deploying your full-stack application to Koyeb using Terraform and GitLab CI/CD streamlines infrastructure management and deployment into an automated, reliable process. By leveraging Terraform’s declarative configuration and GitLab’s powerful pipeline capabilities, you can confidently provision, update, and maintain your deployment environment with minimal manual intervention.&lt;/p&gt;

&lt;p&gt;With the setup covered in this guide, every push to your main branch will trigger an automated workflow that handles infrastructure provisioning and application deployment seamlessly. This approach not only boosts development productivity but also ensures consistent, repeatable deployments.&lt;/p&gt;

&lt;p&gt;If you’re new to Terraform or GitLab pipelines, consider exploring additional resources to deepen your understanding. Once comfortable, you can extend this setup with advanced Terraform modules, multi-environment deployments, or integration with other tools.&lt;/p&gt;

&lt;p&gt;Found this guide helpful? Follow &lt;a href="https://github.com/EphraimX" rel="noopener noreferrer"&gt;EphraimX&lt;/a&gt; for more hands-on DevOps walkthroughs. You can also connect with me on &lt;a href="https://www.linkedin.com/in/oghenefejiro-esosuota" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; or explore more of my work on my &lt;a href="https://ephraim-x-github-io.vercel.app/" rel="noopener noreferrer"&gt;portfolio&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>terraform</category>
      <category>docker</category>
      <category>gitlab</category>
    </item>
    <item>
      <title>How to Deploy a Full Stack App to Koyeb Using Docker Compose and GitLab CI/CD</title>
      <dc:creator>EphraimX</dc:creator>
      <pubDate>Mon, 09 Jun 2025 13:42:10 +0000</pubDate>
      <link>https://dev.to/ephraimx/how-to-deploy-a-full-stack-app-to-koyeb-using-docker-compose-and-gitlab-cicd-2mk3</link>
      <guid>https://dev.to/ephraimx/how-to-deploy-a-full-stack-app-to-koyeb-using-docker-compose-and-gitlab-cicd-2mk3</guid>
      <description>&lt;p&gt;Deploying applications consistently across different CI/CD tools is a skill every engineer should master. This article focuses on how to use &lt;strong&gt;GitLab CI/CD&lt;/strong&gt; to deploy a full stack Docker Compose application to &lt;strong&gt;Koyeb&lt;/strong&gt;, a developer-friendly PaaS.&lt;/p&gt;

&lt;p&gt;If you’ve already followed our &lt;a href="https://dev.to/ephraimx/how-to-deploy-a-full-stack-app-to-koyeb-using-docker-compose-and-github-actions-2oj"&gt;GitHub Actions deployment guide&lt;/a&gt; for the same project, you’re in the right place. We won’t rehash the application structure, Dockerfiles, or Docker Compose file — those details remain unchanged. Instead, this guide zeroes in on the &lt;strong&gt;CI/CD configuration differences specific to GitLab&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You’ll learn how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set up your &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; file for Docker Compose deployments.&lt;/li&gt;
&lt;li&gt;Use the Koyeb CLI within GitLab runners.&lt;/li&gt;
&lt;li&gt;Trigger deployments by pushing to the &lt;code&gt;main&lt;/code&gt; branch.&lt;/li&gt;
&lt;li&gt;Monitor and debug deployment from GitLab’s UI.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;
GitLab CI/CD Configuration

&lt;ul&gt;
&lt;li&gt;Getting the Koyeb API Token&lt;/li&gt;
&lt;li&gt;.gitlab-ci.yml Explained&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Deploying to Koyeb&lt;/li&gt;

&lt;li&gt;Conclusion&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before you begin, ensure the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Review the supporting article:&lt;/strong&gt; If you haven’t already, please read &lt;a href="https://dev.to/ephraimx/how-to-deploy-a-full-stack-app-to-koyeb-using-docker-compose-and-github-actions-2oj"&gt;How to Deploy a Full Stack App to Koyeb Using Docker Compose and GitHub Actions&lt;/a&gt; as it covers the core setup including Dockerfiles and Docker Compose configuration. This article will focus mainly on the GitLab CI/CD pipeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Koyeb account:&lt;/strong&gt; Create a free account at &lt;a href="https://www.koyeb.com" rel="noopener noreferrer"&gt;https://www.koyeb.com&lt;/a&gt; if you don’t have one already.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Koyeb API token:&lt;/strong&gt; You will need this token to authenticate deployments from GitLab. Instructions on how to generate this token will be covered later in this article.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitLab repository:&lt;/strong&gt; Your application code should be pushed to a GitLab repository with the necessary Dockerfiles and Docker Compose configuration included.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Basic knowledge of GitLab CI/CD:&lt;/strong&gt; Familiarity with GitLab pipelines and &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; syntax will help you follow along smoothly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker installed locally (optional):&lt;/strong&gt; To build and test your Docker containers before pushing changes to GitLab.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  GitLab CI/CD Configuration and Koyeb API Token Setup
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Getting Your Koyeb API Token
&lt;/h3&gt;

&lt;p&gt;To deploy your app to Koyeb via GitLab CI/CD, you’ll need an API token to authenticate deployment commands.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Log in to your Koyeb account.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Navigate to &lt;strong&gt;Account Settings&lt;/strong&gt; → &lt;strong&gt;API Tokens&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create New Token&lt;/strong&gt;, give it a meaningful name (e.g., &lt;code&gt;GitLab CI/CD Deployment&lt;/code&gt;), and generate the token.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Copy the token immediately&lt;/strong&gt; — you won’t be able to see it again.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Adding the API Token to GitLab CI/CD Variables
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Open your GitLab project.&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Settings&lt;/strong&gt; → &lt;strong&gt;CI/CD&lt;/strong&gt; → &lt;strong&gt;Variables&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Add Variable&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Enter the following:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Key:&lt;/strong&gt; &lt;code&gt;KOYEB_API_TOKEN&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Value:&lt;/strong&gt; &lt;em&gt;Paste your copied Koyeb API token here&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Enable &lt;strong&gt;Masked&lt;/strong&gt; and &lt;strong&gt;Protected&lt;/strong&gt; to keep the token secure.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Save the variable.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Understanding the &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; File
&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;stages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;build-and-deploy&lt;/span&gt;

&lt;span class="na"&gt;.standard-rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH&lt;/span&gt;

&lt;span class="na"&gt;koyeb-setup-and-deploy-job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build-and-deploy&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;alpine:latest&lt;/span&gt;
  &lt;span class="na"&gt;before_script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;apk add --no-cache curl&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;curl -fsSL https://raw.githubusercontent.com/koyeb/koyeb-cli/master/install.sh | sh&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export PATH=$HOME/.koyeb/bin:$PATH&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export KOYEB_TOKEN=$KOYEB_API_TOKEN&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;koyeb app create glowberry-tax-structure-simulator-glabcicd-docker-compose-koyeb&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;koyeb service create glowberry-tax-structure-simulator-glabcicd-docker-compose-koyeb --app glowberry-tax-structure-simulator-glabcicd-docker-compose-koyeb --git github.com/EphraimX/glowberry-global-tax-structure-simulator-gha-docker-compose-koyeb --instance-type free --git-builder docker --git-docker-dockerfile Dockerfile.koyeb --port 3000:http --route /:3000 --privileged&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;stages:&lt;/strong&gt;
Defines the pipeline stages — here &lt;code&gt;build&lt;/code&gt; and &lt;code&gt;deploy&lt;/code&gt;. In this case, deployment happens in the &lt;code&gt;build&lt;/code&gt; stage since it's a simple flow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;.standard-rules:&lt;/strong&gt;
This reusable rule block ensures the job only runs on the default branch (usually &lt;code&gt;main&lt;/code&gt; or &lt;code&gt;master&lt;/code&gt;). It prevents unnecessary pipeline runs on other branches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;koyeb-setup-and-deploy-job:&lt;/strong&gt;
The core job that builds and deploys the app to Koyeb.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;stage:&lt;/strong&gt; &lt;code&gt;build&lt;/code&gt; — tells GitLab this job belongs to the build stage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;image:&lt;/strong&gt; &lt;code&gt;alpine:latest&lt;/code&gt; — uses a minimal Alpine Linux image as the runner environment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;before_script:&lt;/strong&gt;
Runs before the main script:&lt;/li&gt;
&lt;li&gt;Installs &lt;code&gt;curl&lt;/code&gt; to download the Koyeb CLI.&lt;/li&gt;
&lt;li&gt;Downloads and installs the Koyeb CLI tool from the official repo.&lt;/li&gt;
&lt;li&gt;Updates the system PATH to include the Koyeb CLI executable.&lt;/li&gt;
&lt;li&gt;Sets the &lt;code&gt;KOYEB_TOKEN&lt;/code&gt; environment variable from the secure GitLab variable &lt;code&gt;KOYEB_API_TOKEN&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;script:&lt;/strong&gt;
Runs the actual deployment commands:&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;koyeb app create &amp;lt;app-name&amp;gt;&lt;/code&gt;
Creates a new app on Koyeb with the specified name.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;koyeb service create &amp;lt;service-name&amp;gt; ...&lt;/code&gt;
Creates a service under the app with the following parameters:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--app &amp;lt;app-name&amp;gt;&lt;/code&gt;: Associates the service with the app created above.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--git &amp;lt;repo-url&amp;gt;&lt;/code&gt;: Points to the GitHub repository where your code lives.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--instance-type free&lt;/code&gt;: Specifies the free tier instance for cost-effectiveness.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--git-builder docker&lt;/code&gt;: Instructs Koyeb to use Docker as the build method.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--git-docker-dockerfile Dockerfile.koyeb&lt;/code&gt;: Uses the provided Dockerfile for the build.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--port 3000:http&lt;/code&gt;: Exposes port 3000 as HTTP.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--route /:3000&lt;/code&gt;: Sets the routing path.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--privileged&lt;/code&gt;: Grants elevated permissions if required by your app.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Deploying to Koyeb
&lt;/h2&gt;

&lt;p&gt;To deploy your full stack application to Koyeb using GitLab CI/CD, all you need to do is push your code changes to the &lt;code&gt;main&lt;/code&gt; branch of your GitLab repository. The configured pipeline in &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; will handle the build and deployment automatically.&lt;/p&gt;

&lt;p&gt;Here’s how you can do it from your local machine:&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="c"&gt;# Stage all your changes&lt;/span&gt;
git add &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Commit your changes with a descriptive message&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Deploy full stack app to Koyeb via GitLab CI/CD"&lt;/span&gt;

&lt;span class="c"&gt;# Push changes to the main branch&lt;/span&gt;
git push origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the push is complete:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitLab will detect the commit and trigger the pipeline defined in &lt;code&gt;.gitlab-ci.yml&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Navigate to your project in GitLab, then go to &lt;strong&gt;Build &amp;gt; Pipelines&lt;/strong&gt; to monitor the deployment progress.&lt;/li&gt;
&lt;li&gt;Click on the active pipeline to view detailed logs for each job step, helping you troubleshoot if any errors occur.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pipeline executes the following automatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Installs the Koyeb CLI.&lt;/li&gt;
&lt;li&gt;Authenticates using your &lt;code&gt;KOYEB_API_TOKEN&lt;/code&gt; stored securely in GitLab CI/CD variables.&lt;/li&gt;
&lt;li&gt;Creates or updates your Koyeb app and service by referencing your repository and Docker Compose configuration.&lt;/li&gt;
&lt;li&gt;Deploys your app, making it accessible via the specified routes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to redeploy your application without new code changes, simply trigger the pipeline manually from the &lt;strong&gt;Pipelines&lt;/strong&gt; page in GitLab.&lt;/p&gt;

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

&lt;p&gt;In this guide, you learned how to deploy a full stack application to Koyeb using GitLab CI/CD with Docker Compose. By securely managing your Koyeb API token in GitLab’s CI/CD variables and configuring the &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; pipeline, you can automate your app’s build and deployment seamlessly.&lt;/p&gt;

&lt;p&gt;This setup not only streamlines your deployment process but also ensures consistency and repeatability with every push to your repository. With this foundation, you can confidently iterate on your application while letting GitLab handle the deployment details.&lt;/p&gt;

&lt;p&gt;Feel free to revisit the previous article for a deeper understanding of the Docker Compose setup and application structure. With this knowledge, you’re well equipped to adapt this workflow to other CI/CD platforms or customize it further for your project needs.&lt;/p&gt;

&lt;p&gt;Found this guide helpful? Follow &lt;a href="https://github.com/EphraimX" rel="noopener noreferrer"&gt;EphraimX&lt;/a&gt; for more hands-on DevOps walkthroughs. You can also connect with me on &lt;a href="https://www.linkedin.com/in/oghenefejiro-esosuota" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; or explore more of my work on my &lt;a href="https://ephraim-x-github-io.vercel.app/" rel="noopener noreferrer"&gt;portfolio&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>gitlab</category>
      <category>docker</category>
      <category>koyeb</category>
    </item>
    <item>
      <title>How to Deploy a Full Stack Application to Koyeb Using Pulumi, Docker Compose and GitHub Actions</title>
      <dc:creator>EphraimX</dc:creator>
      <pubDate>Fri, 06 Jun 2025 12:58:46 +0000</pubDate>
      <link>https://dev.to/ephraimx/how-to-deploy-a-docker-compose-app-to-koyeb-using-pulumi-and-github-actions-517b</link>
      <guid>https://dev.to/ephraimx/how-to-deploy-a-docker-compose-app-to-koyeb-using-pulumi-and-github-actions-517b</guid>
      <description>&lt;p&gt;In modern DevOps workflows, Infrastructure as Code (IaC) tools like Pulumi offer powerful, flexible, and developer-friendly ways to define and manage infrastructure using general-purpose programming languages. When paired with CI/CD tools like GitHub Actions, you can fully automate the provisioning and deployment of cloud infrastructure — from code to production — using code you already understand.&lt;/p&gt;

&lt;p&gt;In this guide, you'll learn how to deploy a full stack Docker Compose application to &lt;a href="https://www.koyeb.com" rel="noopener noreferrer"&gt;Koyeb&lt;/a&gt; using &lt;strong&gt;Pulumi&lt;/strong&gt; for infrastructure configuration and &lt;strong&gt;GitHub Actions&lt;/strong&gt; for continuous deployment.&lt;/p&gt;

&lt;p&gt;We’ll assume you’ve already containerized your app and understand basic Docker Compose and CI/CD concepts. If you’re new to those, check out our earlier guide: &lt;a href="https://dev.to/ephraimx/how-to-deploy-a-full-stack-app-to-koyeb-using-docker-compose-and-github-actions-2oj"&gt;Deploying a Full Stack App to Koyeb with Docker Compose and GitHub Actions&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;Setting Up Your Pulumi Project&lt;/li&gt;
&lt;li&gt;Configuring Pulumi to Deploy on Koyeb&lt;/li&gt;
&lt;li&gt;Storing Your Koyeb API Token in GitHub Secrets&lt;/li&gt;
&lt;li&gt;Creating the GitHub Actions Workflow&lt;/li&gt;
&lt;li&gt;Triggering Deployment: Push and Monitor&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before proceeding, ensure you have the following ready:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Read the Docker Compose Deployment Article:&lt;/strong&gt; This article builds on deploying the Glowberry full-stack app with Docker Compose. Please review that &lt;a href="https://dev.to/ephraimx/how-to-deploy-a-full-stack-app-to-koyeb-using-docker-compose-and-github-actions-2oj"&gt;article&lt;/a&gt; if you haven’t already.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Repository:&lt;/strong&gt; Your Glowberry project should be pushed to a GitHub repository with the Docker Compose setup completed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pulumi CLI Installed:&lt;/strong&gt; Install Pulumi on your local machine. See &lt;a href="https://www.pulumi.com/docs/get-started/install/" rel="noopener noreferrer"&gt;Pulumi Installation&lt;/a&gt; for detailed steps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Account:&lt;/strong&gt; Access to your repository with permissions to create GitHub Actions workflows and add secrets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Koyeb Account and API Token:&lt;/strong&gt; Create a Koyeb account if you don’t have one, and generate an API token. You’ll add this token as a secret in GitHub.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Basic familiarity with Infrastructure as Code (IaC):&lt;/strong&gt; Prior experience with Pulumi or similar IaC tools will be helpful but not required.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setting Up Pulumi Infrastructure for Glowberry on Koyeb
&lt;/h2&gt;

&lt;p&gt;To begin managing your Glowberry deployment with Pulumi, create a dedicated directory for your Pulumi infrastructure files:&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;mkdir &lt;/span&gt;pulumi-koyeb
&lt;span class="nb"&gt;cd &lt;/span&gt;pulumi-koyeb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside this folder, you’ll create the following essential files:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;Pulumi.yaml&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This file configures the Pulumi project, specifying its name, runtime, and Python virtual environment settings:&lt;br&gt;
&lt;/p&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;pulumi-koyeb&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Infrastructure for Glowberry Tax Simulator&lt;/span&gt;
&lt;span class="na"&gt;runtime&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;python&lt;/span&gt;
  &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;toolchain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pip&lt;/span&gt;
    &lt;span class="na"&gt;virtualenv&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;venv&lt;/span&gt;
&lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pulumi:tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;pulumi:template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. &lt;code&gt;__main__.py&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This Python script defines the infrastructure as code, using the Pulumi Koyeb provider to create the app and service resources:&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Pulumi Infrastructure for Glowberry Tax Simulator&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pulumi&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pulumi_koyeb&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;koyeb&lt;/span&gt;


&lt;span class="n"&gt;glowberry_application&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;koyeb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gtaxsim-gha-dkr-plkb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gtaxsim-gha-dkr-plkb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;glowberry_application_service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;koyeb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gtaxsim-gha-dkr-plkb-service&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="n"&gt;app_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;glowberry_application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;definition&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;name&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;gtaxsim-gha-dkr-plkb&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;instance_types&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&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;nano&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ports&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;port&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;protocol&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;http&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scalings&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;min&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&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;envs&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="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key&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;PORT&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;value&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;3000&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="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;routes&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;path&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;/&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;port&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&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;regions&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fra&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;git&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;branch&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;main&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;repository&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;github.com/EphraimX/glowberry-global-tax-structure-simulator-gha-docker-compose-pulumi-koyeb&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;dockerfile&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dockerfile&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;Dockerfile.koyeb&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;privileged&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&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;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ResourceOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;depends_on&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;glowberry_application&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;
  
  
  3. &lt;code&gt;requirements.txt&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This file lists all the Python dependencies required for Pulumi and the Koyeb provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Arpeggio==2.0.2
attrs==25.3.0
debugpy==1.8.14
dill==0.4.0
grpcio==1.66.2
parver==0.5
protobuf==4.25.7
pulumi==3.167.0
pulumi_koyeb==0.1.11
PyYAML==6.0.2
semver==3.0.4
setuptools==80.1.0
wheel==0.45.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. &lt;code&gt;.gitignore&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;To keep your repo clean, add the following file to exclude compiled Python files and the virtual environment folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*.pyc
venv/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This setup forms the foundation of your Pulumi infrastructure project to deploy the Glowberry application on Koyeb. Next, you’ll learn how to initialize and configure Pulumi locally before integrating it with GitHub Actions for automated deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initializing and Running Pulumi Locally
&lt;/h2&gt;

&lt;p&gt;Before integrating Pulumi with GitHub Actions, you should first test and deploy your infrastructure locally to ensure everything works as expected.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Set Up a Python Virtual Environment
&lt;/h3&gt;

&lt;p&gt;In your &lt;code&gt;pulumi-koyeb&lt;/code&gt; directory, create and activate a virtual environment to isolate your Python dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv venv
&lt;span class="nb"&gt;source &lt;/span&gt;venv/bin/activate  &lt;span class="c"&gt;# On Windows: venv\Scripts\activate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Install Dependencies
&lt;/h3&gt;

&lt;p&gt;With the virtual environment activated, install the required Python packages from &lt;code&gt;requirements.txt&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;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Install the Pulumi CLI
&lt;/h3&gt;

&lt;p&gt;Make sure you have the Pulumi CLI installed on your system. You can download it from the official site: &lt;a href="https://www.pulumi.com/docs/get-started/install/" rel="noopener noreferrer"&gt;https://www.pulumi.com/docs/get-started/install/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Confirm installation 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;pulumi version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Login to Pulumi
&lt;/h3&gt;

&lt;p&gt;Pulumi requires you to be logged in to manage your stacks. For local testing, use the local backend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pulumi login &lt;span class="nt"&gt;--local&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Configure Koyeb API Token
&lt;/h3&gt;

&lt;p&gt;Pulumi needs your Koyeb API token to authenticate and deploy resources. Set it as a Pulumi config secret:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pulumi config &lt;span class="nb"&gt;set &lt;/span&gt;koyeb:token &amp;lt;YOUR_KOYEB_API_TOKEN&amp;gt; &lt;span class="nt"&gt;--secret&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;&amp;lt;YOUR_KOYEB_API_TOKEN&amp;gt;&lt;/code&gt; with your actual Koyeb API token.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Preview the Infrastructure
&lt;/h3&gt;

&lt;p&gt;Run the following command to see what Pulumi will create or update without making changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pulumi preview
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you a detailed plan of the infrastructure changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Deploy the Infrastructure
&lt;/h3&gt;

&lt;p&gt;If the preview looks good, apply the changes to deploy your Glowberry app on Koyeb:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pulumi up &lt;span class="nt"&gt;--yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pulumi will provision the app and service defined in your &lt;code&gt;__main__.py&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With these steps, you can confidently manage and test your infrastructure locally before automating deployments. Next, we’ll integrate this setup with GitHub Actions for continuous deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up GitHub Actions for Pulumi Deployment
&lt;/h2&gt;

&lt;p&gt;Before creating the GitHub Actions workflow, you need to securely store the required credentials in your GitHub repository settings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add Required Secrets to GitHub
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Get Your Koyeb API Token:&lt;/strong&gt;
Log in to your Koyeb account and navigate to your API tokens page to create or copy your token.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Get Your Pulumi Access Token:&lt;/strong&gt;
Sign in to Pulumi (&lt;a href="https://app.pulumi.com/" rel="noopener noreferrer"&gt;https://app.pulumi.com/&lt;/a&gt;), go to &lt;strong&gt;Settings &amp;gt; Access Tokens&lt;/strong&gt;, and create a new access token.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add Secrets in GitHub:&lt;/strong&gt;
In your GitHub repository, go to &lt;strong&gt;Settings &amp;gt; Secrets and variables &amp;gt; Actions &amp;gt; New repository secret&lt;/strong&gt;, then add:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;KOYEB_TOKEN&lt;/code&gt; — your Koyeb API token&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PULUMI_ACCESS_TOKEN&lt;/code&gt; — your Pulumi access token&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Create the Workflow File
&lt;/h3&gt;

&lt;p&gt;In your repository, create a folder called &lt;code&gt;.github/workflows&lt;/code&gt; (if it doesn’t exist), and inside that folder create a file named &lt;code&gt;glowberry-github-actions-docker-compose-pulumi-koyeb.yml&lt;/code&gt;. Paste the following workflow code into that file:&lt;br&gt;
&lt;/p&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;glowberry-github-actions-docker-compose-pulumi-koyeb&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;pulumi-deploy&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;Checkout Repository Code&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;Koyeb Setup and Deploy&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./pulumi-koyeb&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;sudo apt-get update&lt;/span&gt;
          &lt;span class="s"&gt;sudo apt-get install -y curl&lt;/span&gt;
          &lt;span class="s"&gt;export KOYEB_TOKEN=${{secrets.KOYEB_TOKEN}}&lt;/span&gt;
          &lt;span class="s"&gt;export PULUMI_ACCESS_TOKEN=${{secrets.PULUMI_ACCESS_TOKEN}}&lt;/span&gt;
          &lt;span class="s"&gt;curl -fsSL https://get.pulumi.com | sh&lt;/span&gt;
          &lt;span class="s"&gt;mkdir -p ~/.pulumi/plugins/resource-koyeb-v0.1.11&lt;/span&gt;
          &lt;span class="s"&gt;curl -L https://github.com/koyeb/pulumi-koyeb/releases/download/v0.1.11/pulumi-resource-koyeb-v0.1.11-linux-amd64.tar.gz | tar -xz -C ~/.pulumi/plugins/resource-koyeb-v0.1.11&lt;/span&gt;
          &lt;span class="s"&gt;export PATH=$HOME/.pulumi/bin:$PATH&lt;/span&gt;
          &lt;span class="s"&gt;pulumi login&lt;/span&gt;
          &lt;span class="s"&gt;pulumi stack select glowberry-dev-gha-stack || pulumi stack init glowberry-dev-gha-stack&lt;/span&gt;
          &lt;span class="s"&gt;pulumi preview&lt;/span&gt;
          &lt;span class="s"&gt;pulumi up -y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Workflow Explanation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Trigger:&lt;/strong&gt; This workflow runs on every push to the &lt;code&gt;main&lt;/code&gt; branch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Checkout Step:&lt;/strong&gt; Pulls your repository code to the runner.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Setup and Deploy Step:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Updates the system and installs &lt;code&gt;curl&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Exports your Koyeb and Pulumi tokens from GitHub Secrets as environment variables.&lt;/li&gt;
&lt;li&gt;Installs the Pulumi CLI.&lt;/li&gt;
&lt;li&gt;Downloads and installs the Koyeb Pulumi resource plugin (version 0.1.11).&lt;/li&gt;
&lt;li&gt;Adds Pulumi to the PATH.&lt;/li&gt;
&lt;li&gt;Logs into Pulumi using the CLI.&lt;/li&gt;
&lt;li&gt;Selects or creates a Pulumi stack named &lt;code&gt;glowberry-dev-gha-stack&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Runs &lt;code&gt;pulumi preview&lt;/code&gt; to show what will be deployed.&lt;/li&gt;
&lt;li&gt;Runs &lt;code&gt;pulumi up -y&lt;/code&gt; to deploy the infrastructure automatically.
This CI/CD pipeline automates deploying your Glowberry full-stack app on Koyeb using Pulumi, triggered by code pushes.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pushing Code and Monitoring Your Pulumi Deployment
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Pushing Code to Trigger Deployment
&lt;/h3&gt;

&lt;p&gt;After creating the Pulumi setup and the GitHub Actions workflow file, committing and pushing your changes to the &lt;code&gt;main&lt;/code&gt; branch will automatically trigger the deployment pipeline.&lt;/p&gt;

&lt;p&gt;To push your changes, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Add Pulumi deployment workflow for Koyeb"&lt;/span&gt;
git push origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This push activates the GitHub Actions workflow &lt;code&gt;glowberry-github-actions-docker-compose-pulumi-koyeb.yml&lt;/code&gt;, initiating the deployment of your infrastructure via Pulumi.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monitoring Deployment Progress
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Open Your GitHub Repository:&lt;/strong&gt;
Navigate to your repository on GitHub.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access the Actions Tab:&lt;/strong&gt;
Click on the &lt;strong&gt;Actions&lt;/strong&gt; tab in the repository menu to view recent workflow runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Locate the Pulumi Deployment Workflow:&lt;/strong&gt;
Find the latest run titled &lt;code&gt;glowberry-github-actions-docker-compose-pulumi-koyeb&lt;/code&gt; triggered by your push.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inspect the Workflow Run:&lt;/strong&gt;
Click on the workflow run to see the detailed job and step logs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review the Logs for Deployment Status:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Confirm the checkout step succeeded.&lt;/li&gt;
&lt;li&gt;Verify installation of Pulumi CLI and Koyeb Pulumi plugin without errors.&lt;/li&gt;
&lt;li&gt;Check the output of &lt;code&gt;pulumi preview&lt;/code&gt; to understand the infrastructure changes planned.&lt;/li&gt;
&lt;li&gt;Monitor the &lt;code&gt;pulumi up&lt;/code&gt; step to ensure successful deployment or updates to your application on Koyeb.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If any step fails, the logs provide detailed error messages to help you troubleshoot.&lt;/p&gt;

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

&lt;p&gt;In this guide, you’ve learned how to deploy a full-stack application on Koyeb using Pulumi for infrastructure as code and GitHub Actions to automate your CI/CD pipeline. This approach offers powerful infrastructure management with Pulumi’s flexible Python SDK, combined with the automation and integration capabilities of GitHub Actions.&lt;/p&gt;

&lt;p&gt;By setting up Pulumi locally and configuring the GitHub workflow to run your deployment, you gain a streamlined, repeatable process to provision and update your cloud resources efficiently. You can easily extend this foundation to support more complex infrastructure needs or integrate with other tools in your development workflow.&lt;/p&gt;

&lt;p&gt;Found this guide helpful? Follow &lt;a href="https://github.com/EphraimX" rel="noopener noreferrer"&gt;EphraimX&lt;/a&gt; for more hands-on DevOps walkthroughs. You can also connect with me on &lt;a href="https://www.linkedin.com/in/oghenefejiro-esosuota" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; or explore more of my work on my &lt;a href="https://ephraim-x-github-io.vercel.app/" rel="noopener noreferrer"&gt;portfolio&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>githubactions</category>
      <category>pulumi</category>
      <category>koyeb</category>
    </item>
  </channel>
</rss>
