<?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: Felipe Arcaro</title>
    <description>The latest articles on DEV Community by Felipe Arcaro (@felipearcaro).</description>
    <link>https://dev.to/felipearcaro</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%2F1146439%2Febaa7f27-a538-4174-a021-9ff56f29daa5.png</url>
      <title>DEV Community: Felipe Arcaro</title>
      <link>https://dev.to/felipearcaro</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/felipearcaro"/>
    <language>en</language>
    <item>
      <title>Can I scale my dockerized Flask solution with Kubernetes?</title>
      <dc:creator>Felipe Arcaro</dc:creator>
      <pubDate>Sat, 06 Apr 2024 12:14:05 +0000</pubDate>
      <link>https://dev.to/felipearcaro/can-i-scale-my-dockerized-flask-solution-with-kubernetes-289l</link>
      <guid>https://dev.to/felipearcaro/can-i-scale-my-dockerized-flask-solution-with-kubernetes-289l</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Yes - with some work.&lt;/p&gt;

&lt;p&gt;Here are the steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install &lt;a href="https://kompose.io/"&gt;Kompose&lt;/a&gt; - a conversion tool that allows you to convert your Docker Compose code to Kubernetes configuration files

&lt;ul&gt;
&lt;li&gt;Run &lt;code&gt;kompose convert&lt;/code&gt; in the same directory as your &lt;code&gt;docker-compose.yml&lt;/code&gt; to generate the config files for your Kubernetes cluster&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Install &lt;a href="https://github.com/kubernetes/minikube"&gt;Minicube&lt;/a&gt; - a tool that allows us to spin up a Kubernetes cluster in a local machine

&lt;ul&gt;
&lt;li&gt;Run &lt;code&gt;minikube start&lt;/code&gt; to start your Kubernetes cluster&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;minikube dashboard&lt;/code&gt; to spin up a web-based user interface that allows you to manage your Kubernetes cluster&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Install &lt;a href="https://kubernetes.io/docs/reference/kubectl/"&gt;kubectl&lt;/a&gt; - a CLI tool provided by Kubernetes for communicating with a Kubernetes cluster's control plane using the Kubernetes API

&lt;ul&gt;
&lt;li&gt;Run &lt;code&gt;kubectl apply -f &amp;lt;filename&amp;gt;&lt;/code&gt; to apply your services/deployments&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Let's consider the following scenario - our application has gained popularity and is attracting a large number of users and, to keep up with that, we need a way of handling increased traffic.&lt;/p&gt;

&lt;p&gt;We could vertically scale - that is, allocate more resources to our containers:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8bg0LNLj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://felipearcaro.github.io/images/app.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8bg0LNLj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://felipearcaro.github.io/images/app.png" alt="app" width="800" height="343"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But instead, we want to horizontally scale it by creating multiple copies of the containers and distributing traffic among them:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--x4wrK3Or--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://felipearcaro.github.io/images/app-horizontal.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--x4wrK3Or--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://felipearcaro.github.io/images/app-horizontal.png" alt="app-horizontal" width="800" height="492"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And as you probably guessed by the title of this post, we're going to use Kubernetes for that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wait... isn't Kubernetes and Docker the same thing?
&lt;/h2&gt;

&lt;p&gt;Not really. Docker focuses on creating and running containers, while Kubernetes is a container orchestration platform that manages the deployment, scaling, and operations of those containers in a cluster.&lt;/p&gt;

&lt;p&gt;You can think of Docker as the chef who prepares individual dishes (containers) in the kitchen, ensuring they're well-packaged and portable, while Kubernetes serves as the restaurant manager who orchestrates the dining experience, seating guests (users) at tables (containers), coordinating servers (nodes), and managing the overall flow of the restaurant to ensure a delightful dining service..&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Kubernetes?
&lt;/h2&gt;

&lt;p&gt;Kubernetes, often referred to as k8s, is an open-source container orchestration engine designed to automate the deployment, scaling, and management of containerized applications. It aims to simplify application management, ensure high availability via replica sets, and facilitate scalability. &lt;/p&gt;

&lt;p&gt;By abstracting the underlying infrastructure, Kubernetes offers a declarative approach to define the desired state of applications and their dependencies using YAML files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kubernetes' architecture
&lt;/h2&gt;

&lt;p&gt;Deploying Kubernetes creates a cluster, consisting of worker nodes running containerized applications. Each cluster has at least one worker node, hosting Pods, which form the application workload. The control plane manages these nodes and Pods. In production, the control plane typically runs across multiple computers for fault-tolerance and high availability.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kYIgBobH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://felipearcaro.github.io/images/kubernetes%2520architecture.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kYIgBobH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://felipearcaro.github.io/images/kubernetes%2520architecture.png" alt="kubernetes architecture" width="800" height="386"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's take a quick look at Kubernetes' main components.&lt;/p&gt;

&lt;h3&gt;
  
  
  Control Plane:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;kube-apiserver: Exposes the Kubernetes API for cluster management&lt;/li&gt;
&lt;li&gt;etcd: Stores all cluster data reliably&lt;/li&gt;
&lt;li&gt;kube-scheduler: Assigns nodes for newly created Pods&lt;/li&gt;
&lt;li&gt;kube-controller-manager: Runs controller processes to manage cluster state.&lt;/li&gt;
&lt;li&gt;cloud-controller-manager: Integrates cloud-specific control logic (if applicable).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Node:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;kubelet Ensures containers described in PodSpecs are running and healthy.&lt;/li&gt;
&lt;li&gt;kube-proxy: Maintains network rules for network communication to Pods.&lt;/li&gt;
&lt;li&gt;Container runtime: Manages execution and lifecycle of containers within Kubernetes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can learn more about it on &lt;a href="https://kubernetes.io/docs/concepts/overview/components/"&gt;Kubernete's official page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting to work
&lt;/h2&gt;

&lt;p&gt;Enough theory, let's get to work.&lt;/p&gt;

&lt;p&gt;For this experiment, we're going to need three tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://kompose.io/"&gt;Kompose&lt;/a&gt; - a conversion tool that allows us to convert our Docker Compose code to Kubernetes configuration files&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/kubernetes/minikube"&gt;Minicube&lt;/a&gt; - a tool that allows us to spin up a Kubernetes cluster in a local machine&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://kubernetes.io/docs/reference/kubectl/"&gt;kubectl&lt;/a&gt; - a CLI tool provided by Kubernetes for communicating with a Kubernetes cluster's control plane using the Kubernetes API&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Converting our &lt;code&gt;docker-compose.yml&lt;/code&gt; file
&lt;/h3&gt;

&lt;p&gt;Once we have all of those installed, we will start converting our &lt;code&gt;docker-compose&lt;/code&gt; file running &lt;code&gt;kompose convert&lt;/code&gt; in the same directory as our &lt;code&gt;docker-compose.yml&lt;/code&gt; file. &lt;/p&gt;

&lt;p&gt;A few files were created in that directory:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;env-configmap.yaml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mongo-db-claim0-persistentvolumeclaim.yaml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mongo-db-claim1-persistentvolumeclaim.yaml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mongo-db-service.yaml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mongo-db-deployment.yaml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;app-service.yaml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;app-deployment.yaml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;schedule-service-claim0-persistentvolumeclaim.yaml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;schedule-service-claim1-persistentvolumeclaim.yaml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;schedule-service-service.yaml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;schedule-service-deployment.yaml&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Firing it all up
&lt;/h3&gt;

&lt;p&gt;I am not super familiar with &lt;a href="https://helm.sh/"&gt;Helm&lt;/a&gt; but I think one of its features is to manage dependencies between those files, allowing us to run all of them in a &lt;code&gt;docker-compose up&lt;/code&gt; style. &lt;/p&gt;

&lt;p&gt;For this post, let's do everything manually and cover the details on the &lt;code&gt;app&lt;/code&gt; files.&lt;/p&gt;

&lt;p&gt;We'll start exposing our variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;kubectl apply -f env-configmap.yaml&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then spinning up up our Mongo DB service running: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; &lt;code&gt;kubectl apply -f mongo-db-claim0-persistentvolumeclaim.yaml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kubectl apply -f mongo-db-claim1-persistentvolumeclaim.yaml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kubectl apply -f mongo-db-service.yaml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kubectl apply -f mongo-db-deployment.yaml&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And now, we can spin up our Flask application:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;kubectl apply -f app-service.yaml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kubectl apply -f app-deployment.yaml&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's take a deeper looker into these two files files:&lt;/p&gt;

&lt;p&gt;app-service.yaml:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: v1
kind: Service
metadata:
  annotations:
    kompose.cmd: kompose convert
    kompose.version: 1.32.0 (HEAD)
  labels:
    io.kompose.service: app
  name: app
spec:
  ports:
    - name: '8001'
      port: 8001
      targetPort: 8001
  selector:
    io.kompose.service: app

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;app-deployment.yaml:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    kompose.cmd: kompose convert
    kompose.version: 1.32.0 (HEAD)
  labels:
    io.kompose.service: app
  name: app
spec:
  replicas: 3
  selector:
    matchLabels:
      io.kompose.service: app
  template:
    metadata:
      annotations:
        kompose.cmd: kompose convert
        kompose.version: 1.32.0 (HEAD)
      labels:
        io.kompose.network/test-kompose-my-network: 'true'
        io.kompose.service: app
    spec:
      containers:
        - args:
            - flask
            - run
            - '--host'
            - 0.0.0.0
            - '--port'
            - '8001'
          env:
            - name: ENV
              valueFrom:
                configMapKeyRef:
                  key: ENV
                  name: env
            - name: FLASK_SECRET_KEY
              valueFrom:
                configMapKeyRef:
                  key: FLASK_SECRET_KEY
                  name: env
            - name: MONGO_INITDB_DATABASE
              valueFrom:
                configMapKeyRef:
                  key: MONGO_INITDB_DATABASE
                  name: env
            - name: MONGO_INITDB_ROOT_PASSWORD
              valueFrom:
                configMapKeyRef:
                  key: MONGO_INITDB_ROOT_PASSWORD
                  name: env
            - name: MONGO_INITDB_ROOT_USERNAME
              valueFrom:
                configMapKeyRef:
                  key: MONGO_INITDB_ROOT_USERNAME
                  name: env
            - name: MONGO_PASSWORD
              valueFrom:
                configMapKeyRef:
                  key: MONGO_PASSWORD
                  name: env
            - name: MONGO_USER
              valueFrom:
                configMapKeyRef:
                  key: MONGO_USER
                  name: env
            - name: QUOTES_APP_MONGO_CONN
              valueFrom:
                configMapKeyRef:
                  key: QUOTES_APP_MONGO_CONN
                  name: env
            - name: SENDGRID_API_KEY
              valueFrom:
                configMapKeyRef:
                  key: SENDGRID_API_KEY
                  name: env
          image: local-app-image
          imagePullPolicy: IfNotPresent
          name: my-app
          ports:
            - containerPort: 8001
              hostPort: 8001
              protocol: TCP
      restartPolicy: Always

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most parameters here are self-explanatory, but I want to highlight two of them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;imagePullPolicy&lt;/code&gt; - we need to set that to &lt;code&gt;Never&lt;/code&gt; or &lt;code&gt;IfNotPresent&lt;/code&gt; if we intend to run images we built locally. We also need to run &lt;code&gt;eval $(minikube docker-env)&lt;/code&gt; in the same terminal I'm running &lt;code&gt;kubectl&lt;/code&gt; commands as stated in the &lt;a href="https://minikube.sigs.k8s.io/docs/handbook/pushing/#1-pushing-directly-to-the-in-cluster-docker-daemon-docker-env"&gt;official documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;replicas&lt;/code&gt; - we can choose the number of containers we want to spin up (nifty stuff)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lastly, we spin up the schedule service:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; &lt;code&gt;kubectl apply -f schedule-service-claim0-persistentvolumeclaim.yaml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kubectl apply -f schedule-service-claim1-persistentvolumeclaim.yaml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kubectl apply -f schedule-service-service.yaml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kubectl apply -f schedule-service-deployment.yaml&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Taking a look at our creation
&lt;/h3&gt;

&lt;p&gt;To see what we just did, we could go old school with &lt;code&gt;kubectl get pods&lt;/code&gt; (and some other commands) on the terminal. &lt;/p&gt;

&lt;p&gt;But Minikube has a web-based user interface that allows us to manage our Kubernetes'cluster in the browser. We can simply run &lt;code&gt;minikube dashboard&lt;/code&gt; and go to &lt;a href="http://127.0.0.1:52084/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/#/workloads?namespace=default"&gt;this link&lt;/a&gt; on any browser.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2jU98OT8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://felipearcaro.github.io/images/Screenshot%25202024-04-06%2520at%252008.10.48.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2jU98OT8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://felipearcaro.github.io/images/Screenshot%25202024-04-06%2520at%252008.10.48.png" alt="Screenshot 2024-04-06 at 08.10.48" width="800" height="423"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here are some of the things we can do in that dashboard:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Monitor the health of our applications and cluster&lt;/li&gt;
&lt;li&gt;Create, view, update, and delete Kubernetes resources&lt;/li&gt;
&lt;li&gt;View logs of our applications&lt;/li&gt;
&lt;li&gt;Access and manage secrets, config maps, and other configurations.&lt;/li&gt;
&lt;li&gt;Debug applications directly from the dashboard&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Many folks find Kubernetes to be complex, and some even say that those who praise it might not actually use it regularly. &lt;/p&gt;

&lt;p&gt;Although Kubernetes has become easier to use in some ways, setting it up still needs some previous knowledge, especially when it comes to configuring networks for load balancers to handle pods replicas correctly.&lt;/p&gt;

&lt;p&gt;But it's pretty amazing how quickly you can get a Kubernetes environment running on your own computer. This is super helpful when you're ready to move our applications to platforms like AWS EKS - it makes testing and development easier and sets you up for a smoother deployment in the real world.&lt;/p&gt;

</description>
      <category>flask</category>
      <category>kubernetes</category>
      <category>docker</category>
    </item>
    <item>
      <title>Accessing my home server from a different continent (the hard way)</title>
      <dc:creator>Felipe Arcaro</dc:creator>
      <pubDate>Sat, 30 Mar 2024 20:54:27 +0000</pubDate>
      <link>https://dev.to/felipearcaro/accessing-my-home-server-from-a-different-continent-the-hard-way-np0</link>
      <guid>https://dev.to/felipearcaro/accessing-my-home-server-from-a-different-continent-the-hard-way-np0</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;I have a home server in Porto, Portugal. When I needed to travel to Brazil for 4 months, I wanted to access my home server from a different network. While there are several tools available for this purpose, I decided to hack my way through it because... why not?&lt;/p&gt;

&lt;p&gt;So, I took the following steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Wrote a script to periodically send me emails informing me if my home server's external IP had changed&lt;/li&gt;
&lt;li&gt;Configured the required port forwarding on my router&lt;/li&gt;
&lt;li&gt;Set up SSH keys between my laptop and my home server&lt;/li&gt;
&lt;li&gt;Implemented some monitoring&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  My home server's IP
&lt;/h2&gt;

&lt;p&gt;If you've ever tinkered with your home network, you've probably noticed that each device within your house is assigned an internal IP address, allowing them to communicate with each other. However, all these devices share access to the internet through a single IP address provided by your Internet Service Provider (ISP). You can easily find this external IP address on websites like &lt;a href="https://whatismyipaddress.com/"&gt;this&lt;/a&gt;, but it's important to note that this IP address might change over time.&lt;/p&gt;

&lt;p&gt;While there are tools like &lt;a href="https://www.duckdns.org/"&gt;DuckDNS&lt;/a&gt; available to manage dynamic IP changes, I decided to create my own hacky solution – I wrote a script to periodically check if my IP had changed and send me emails using SendGrid.&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="c"&gt;# Constants&lt;/span&gt;
&lt;span class="nv"&gt;SENDGRID_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"MY_KEY"&lt;/span&gt;
&lt;span class="nv"&gt;TO_EMAIL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"my_email@gmail.com"&lt;/span&gt;
&lt;span class="nv"&gt;FROM_EMAIL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"my_email@gmail.com"&lt;/span&gt;
&lt;span class="nv"&gt;CURRENT_IP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"my_ip"&lt;/span&gt;

&lt;span class="c"&gt;# Get the external IP address using an external service&lt;/span&gt;
&lt;span class="nv"&gt;EXTERNAL_IP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://api.ipify.org&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Compose the email content&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$EXTERNAL_IP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CURRENT_IP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nv"&gt;EMAIL_BODY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Yo, your home server IP address is still the same: &lt;/span&gt;&lt;span class="nv"&gt;$EXTERNAL_IP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;else
        &lt;/span&gt;&lt;span class="nv"&gt;EMAIL_BODY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Yo, it looks like your home server IP address has changed: &lt;/span&gt;&lt;span class="nv"&gt;$EXTERNAL_IP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nv"&gt;EMAIL_SUBJECT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Home Server - Weekly IP check"&lt;/span&gt;

&lt;span class="c"&gt;# Create a temporary JSON payload file&lt;/span&gt;
&lt;span class="nv"&gt;TMP_JSON&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;mktemp&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{
  "personalizations": [
    {
      "to": [
        {
          "email": "'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TO_EMAIL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;'"
        }
      ],
      "subject": "'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$EMAIL_SUBJECT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;'"
    }
  ],
  "from": {
    "email": "'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FROM_EMAIL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;'"
  },
  "content": [
    {
      "type": "text/plain",
      "value": "'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$EMAIL_BODY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;'"
    }
  ]
}'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP_JSON&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Send email using SendGrid API&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://api.sendgrid.com/v3/mail/send"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$SENDGRID_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;--data-binary&lt;/span&gt; @&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP_JSON&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Clean up temporary JSON file&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP_JSON&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I scheduled the execution of that script using a cronjob (Linux). &lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up port forwarding
&lt;/h2&gt;

&lt;p&gt;Remember I mentioned all our home devices' traffic exits through a single IP? Well, it works the other way around too – we can use that same IP to access our home devices, with a little help. Most routers come with a firewall that blocks incoming traffic for security reasons. &lt;/p&gt;

&lt;p&gt;However, they also offer port forwarding, a feature that allows us to specify rules like "if someone tries to access my IP on port 123, redirect them to this internal IP (specific device) on the same port". &lt;/p&gt;

&lt;p&gt;Setting up port forwarding involves accessing our router's settings through a web browser, logging in with your credentials, and configuring the port forwarding rules accordingly. SSH (Secure Shell) connections typically use port &lt;code&gt;22&lt;/code&gt;by default. I decided to use a less common port to avoid attacks targeting the default port.&lt;/p&gt;

&lt;p&gt;With port forwarding in place, we are ready to access my home server using SSH with &lt;code&gt;ssh my_user@my_external_ip&lt;/code&gt; and entering my password, right? Well, technically yes, but passwords are no fun (and not really secure). That's why it's time to set up SSH keys for extra security.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring SSH keys
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Creating keys on my laptop
&lt;/h3&gt;

&lt;p&gt;First, I generated SSH keys on my laptop by running the following command in the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh-keygen &lt;span class="nt"&gt;-t&lt;/span&gt; rsa &lt;span class="nt"&gt;-b&lt;/span&gt; 4096 &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"email@example.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, both the public and private keys are stored in our home directory within a hidden folder named &lt;code&gt;.ssh&lt;/code&gt;. The private key is typically named &lt;code&gt;id_rsa&lt;/code&gt;, while the public key has the same name but with a &lt;code&gt;.pub&lt;/code&gt; extension (&lt;code&gt;id_rsa.pub&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;To view and copy the newly created public key, we can use the command &lt;code&gt;cat ~/.ssh/id_rsa.pub&lt;/code&gt;, which will display something like this: &lt;code&gt;ssh-rsa &amp;lt;long-string&amp;gt; email@example.com&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding the public key to my home server
&lt;/h3&gt;

&lt;p&gt;Next, now on my home server, I log in with the desired user for SSH connections, and create the &lt;code&gt;.ssh&lt;/code&gt; folder and &lt;code&gt;authorized_keys&lt;/code&gt; file if they don'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; /home/my_user/.ssh &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;touch&lt;/span&gt; /home/my_user/.ssh/authorized_keys
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, I paste my public key on &lt;code&gt;authorized_keys&lt;/code&gt; file using a text editor.&lt;/p&gt;

&lt;p&gt;Finally, we ensure that the permissions on the &lt;code&gt;~/.ssh&lt;/code&gt; directory and the &lt;code&gt;authorized_keys&lt;/code&gt; file are correctly set for SSH to use them:&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;chmod &lt;/span&gt;700 /home/my_user/.ssh
&lt;span class="nb"&gt;chmod &lt;/span&gt;600 /home/my_user/.ssh/authorized_keys
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These permissions restrict access to the &lt;code&gt;.ssh&lt;/code&gt; directory and its contents to only the user associated with it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Removing password access
&lt;/h3&gt;

&lt;p&gt;Now that we have properly set up the SSH keys, it's time to remove the password access. For that, we simply change set &lt;code&gt;PasswordAuthentication no&lt;/code&gt; inside &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt; and restart the SSH service with &lt;code&gt;sudo service ssh restart&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring
&lt;/h2&gt;

&lt;p&gt;Although this workflow seems fairly secure, it's always a good idea monitor SSH access. My home server runs on Ubuntu, so I SSH (and other) activity is logged on &lt;code&gt;/var/log/auth.log&lt;/code&gt; file. &lt;/p&gt;

&lt;p&gt;I created a simple script to check for any changes in that 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="c"&gt;# Constants&lt;/span&gt;
&lt;span class="nv"&gt;SENDGRID_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"MY_KEY"&lt;/span&gt;
&lt;span class="nv"&gt;TO_EMAIL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"my_email@gmail.com"&lt;/span&gt;
&lt;span class="nv"&gt;FROM_EMAIL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"my_email@gmail.com"&lt;/span&gt;
&lt;span class="nv"&gt;CURRENT_IP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"my_ip"&lt;/span&gt;

send_email_notification&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="c"&gt;# Create a temporary JSON payload file&lt;/span&gt;
        &lt;span class="nv"&gt;TMP_JSON&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;mktemp&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{
          "personalizations": [
            {
              "to": [
                {
                  "email": "'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TO_EMAIL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;'"
                }
              ],
              "subject": "'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$EMAIL_SUBJECT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;'"
            }
          ],
          "from": {
            "email": "'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FROM_EMAIL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;'"
          },
          "content": [
            {
              "type": "text/plain",
              "value": "'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$EMAIL_BODY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;'"
            }
          ]
        }'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP_JSON&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

        &lt;span class="c"&gt;# Send email using SendGrid API&lt;/span&gt;
        curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://api.sendgrid.com/v3/mail/send"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
             &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$SENDGRID_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
             &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
             &lt;span class="nt"&gt;--data-binary&lt;/span&gt; @&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP_JSON&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

        &lt;span class="c"&gt;# Clean up temporary JSON file&lt;/span&gt;
        &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP_JSON&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Define the critical file to monitor&lt;/span&gt;
&lt;span class="nv"&gt;ssh_logs_file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/var/log/auth.log"&lt;/span&gt;

&lt;span class="c"&gt;# Store the initial checksums&lt;/span&gt;
&lt;span class="nv"&gt;initial_md5sum&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;md5sum&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ssh_logs_file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Initial md5sum: &lt;/span&gt;&lt;span class="nv"&gt;$initial_md5sum&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c"&gt;# Calculate the current checksums&lt;/span&gt;
    &lt;span class="nv"&gt;current_md5sum&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;md5sum&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ssh_logs_file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;# Compare checksums to detect changes&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$initial_md5sum&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$current_md5sum&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;send_email_notification
        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Oops, it seems like checksum is different, new logs were added to ssh auth.log"&lt;/span&gt;
        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Initial md5sum: &lt;/span&gt;&lt;span class="nv"&gt;$initial_md5sum&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Current md5sum: &lt;/span&gt;&lt;span class="nv"&gt;$current_md5sum&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="nv"&gt;initial_md5sum&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$current_md5sum&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;fi&lt;/span&gt;

    &lt;span class="c"&gt;# Adjust the sleep interval&lt;/span&gt;
    &lt;span class="nb"&gt;sleep &lt;/span&gt;300
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I ran the 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="nb"&gt;chmod&lt;/span&gt; +x check-ssh-logs.sh 
 &lt;span class="nb"&gt;nohup&lt;/span&gt; ./check-ssh-logs.sh &amp;amp; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;nohup&lt;/code&gt; is a nifty command used to run a process immune to hangups, allowing it to continue running even after logging out&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then I got married, traveled back to Portugal, had to move to a different apartment and haven't turned on my home server ever since. &lt;/p&gt;

</description>
      <category>ssh</category>
      <category>homeserver</category>
      <category>bash</category>
    </item>
    <item>
      <title>Dockerizing a Flask application</title>
      <dc:creator>Felipe Arcaro</dc:creator>
      <pubDate>Sat, 30 Mar 2024 11:46:29 +0000</pubDate>
      <link>https://dev.to/felipearcaro/dockerizing-a-flask-application-2e5e</link>
      <guid>https://dev.to/felipearcaro/dockerizing-a-flask-application-2e5e</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Here's the &lt;code&gt;docker-compose.yml&lt;/code&gt; file to dockerize a Flask application, a Mongo database and a schedule service:&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.8'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my_app_${ENV}&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&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="s"&gt;8001:8001&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mongo_db&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;my_network&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;flask"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;run"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--host"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--port"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8001"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.env&lt;/span&gt;

  &lt;span class="na"&gt;schedule_service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;schedule_service_${ENV}&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./scheduler&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;./common:/app/common&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./db:/app/db&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;my_networknetwork&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;python"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-u"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scheduler.py"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt; 
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.env&lt;/span&gt;

  &lt;span class="na"&gt;mongo_db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mongo_db_${ENV}&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;mongo:4.4.18&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;always&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="s"&gt;27017:27017&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;./DB-STORAGE/mongo-db-$ENV:/data/db&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./db/init-mongo.sh:/docker-entrypoint-initdb.d/init-mongo.sh&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;my_network&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.env&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;my_network&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the following command from your project's root directory &lt;code&gt;docker-compose up --build -d&lt;/code&gt; to fire it all up.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is Docker?
&lt;/h2&gt;

&lt;p&gt;Docker is a tool that helps us package up our apps so they can run smoothly no matter where they're deployed – it's like putting our app in a virtual box that contains everything it needs to work - code, settings, and libraries. These boxes are called "containers," and they're lightweight and easy to move around.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;With Docker, there's no such a thing as "but it works on my machine..."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Getting started with Dockerfile and Docker Compose
&lt;/h2&gt;

&lt;p&gt;To spin up a container, we can do it directly from the terminal or we can create a file called &lt;code&gt;Dockerfile&lt;/code&gt;, which works like a recipe for our app's container. In the &lt;code&gt;Dockerfile&lt;/code&gt;, we specify what our app needs to run, like the programming language and dependencies.&lt;/p&gt;

&lt;p&gt;If we want to spin up more containers at once, we can use Docker Compose. Docker Compose reads a configuration file – usually called &lt;code&gt;docker-compose.yml&lt;/code&gt;, that describes all the containers our app needs to run and how they should interact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why should we dockerize an application?
&lt;/h2&gt;

&lt;p&gt;Dockerizing an application offers several benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It enhances collaboration and streamlines development workflows – we can work in isolated environments without conflicts, speeding up development and making it easier to onboard new team members&lt;/li&gt;
&lt;li&gt;It makes our app more portable – we can easily move it between different environments without worrying about compatibility issues which simplifies deployment and ensures consistency across different platforms&lt;/li&gt;
&lt;li&gt;It improves scalability and resource management - we can easily start/stop containers instances to accommodate fluctuations in traffic&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Dockerizing a Flask application
&lt;/h2&gt;

&lt;p&gt;Let's say we've got a Flask application with four main components: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;network: custom Docker network&lt;/li&gt;
&lt;li&gt;app: our Flask application&lt;/li&gt;
&lt;li&gt;mongo_db: a Mongo database&lt;/li&gt;
&lt;li&gt;schedule: an email schedule service &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8bg0LNLj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://felipearcaro.github.io/images/app.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8bg0LNLj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://felipearcaro.github.io/images/app.png" alt="app" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's break the Docker Compose code down to understand these components at parameter level.&lt;/p&gt;

&lt;h3&gt;
  
  
  network
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;networks:
  quot-network:  
    driver: bridge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;networks&lt;/code&gt; is used to define custom Docker networks in Docker Compose, it allows us to create separate networks for our services, and it helps facilitate communication between the containers running on those networks&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;my_network&lt;/code&gt; is the name of the custom network being defined&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;driver&lt;/code&gt; specifies the network driver to be used for the custom network&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A few notes on Docker networks:&lt;br&gt;
    - The &lt;code&gt;bridge&lt;/code&gt; driver is the default network driver in Docker and is suitable for most use cases. It enables communication between containers on the same host and provides automatic DNS resolution for service discovery. Each custom network with the "bridge" driver is isolated from the host's default bridge network and other custom bridge networks&lt;br&gt;
    - When using the &lt;code&gt;bridge&lt;/code&gt; driver, containers on the same network can communicate with each other using their container names as hostnames (e.g., &lt;code&gt;schedule&lt;/code&gt;)&lt;br&gt;
    - Using the &lt;code&gt;host&lt;/code&gt; network instead of &lt;code&gt;bridge&lt;/code&gt; allows a container to share the network namespace with the host system. This means the container shares the host's network stack, and network ports used by the container are directly mapped to the host&lt;/p&gt;
&lt;h3&gt;
  
  
  app
&lt;/h3&gt;


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

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my_app_${ENV}&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&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="s"&gt;8001:8001&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mongo_db&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;my_network&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;flask"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;run"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--host"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--port"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8001"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.env&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;version: '3.8'&lt;/code&gt; specifies the version of the Docker Compose file syntax being used - in this case, it's version 3.8&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;services&lt;/code&gt; is the top-level key that defines the services/containers that will be managed by Docker Compose&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;app&lt;/code&gt; the name of the service/container being defined&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;container_name&lt;/code&gt; specifies the custom name for the container that will be created based on this service. The variable &lt;code&gt;${ENV}&lt;/code&gt; dynamically sets the suffix based on an environment variable. For example, if the value of &lt;code&gt;${ENV}&lt;/code&gt; is "production", the container name will be &lt;code&gt;my_app_production&lt;/code&gt;.

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;.env&lt;/code&gt; file should be placed at the root of the project directory next to our &lt;code&gt;docker-compose.yml&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;build&lt;/code&gt; indicates that the service will be built using a Dockerfile located in the current directory (denoted by &lt;code&gt;.&lt;/code&gt;). The &lt;code&gt;context&lt;/code&gt; parameter defines the build context, which means the current directory and its subdirectories will be used to find the necessary files for the build&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ports&lt;/code&gt; exposes ports from the container to the host - it will map port &lt;code&gt;8001&lt;/code&gt; from the host to port &lt;code&gt;8001&lt;/code&gt; in the container, that means any traffic coming to port &lt;code&gt;8001&lt;/code&gt; on the host will be forwarded to port &lt;code&gt;8001&lt;/code&gt; of the container&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;depends_on&lt;/code&gt; specifies that this service depends on another service called &lt;code&gt;mongo_db&lt;/code&gt;. It ensures that the &lt;code&gt;mongo_db&lt;/code&gt; is up and running before this service starts&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;networks&lt;/code&gt; attaches the service to a pre-existing Docker network, this allows both the &lt;code&gt;app&lt;/code&gt; service and other services connected to communicate with each other&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;command&lt;/code&gt; overrides the default command in the Dockerfile that would be executed when the container starts. The app will run with the following parameters: &lt;code&gt;flask run --host 0.0.0.0 --port 8001&lt;/code&gt;, meaning Flask will listen on all available network interfaces (&lt;code&gt;0.0.0.0&lt;/code&gt;) and port &lt;code&gt;8001&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;When we define the &lt;code&gt;command&lt;/code&gt; parameter in the Docker Compose file for a service, it takes precedence over the default &lt;code&gt;CMD&lt;/code&gt; command specified in the Dockerfile&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;env_file&lt;/code&gt; specifies the file from which environment variables should be read and passed to the container&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For reference, this is the service's Dockerfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;python:3.8-slim-buster&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;# Create app directory&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;# Install Python requirements first so it's cached&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;pip3 &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="c"&gt;# Copy Flask project to container&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;# Set Flask configurations&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; FLASK_APP=app.py&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; FLASK_RUN_HOST=0.0.0.0&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;DEV&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;pip3 &lt;span class="nb"&gt;install &lt;/span&gt;debugger
&lt;span class="k"&gt;RUN &lt;/span&gt;pip3 &lt;span class="nb"&gt;install &lt;/span&gt;debugpy

&lt;span class="c"&gt;# Define Flask environment (production is default)&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; FLASK_ENV=development &lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["python", "-m", "debugpy",\&lt;/span&gt;
    "--listen", "0.0.0.0:5678",\
    "--wait-for-client",\
    "-m", "flask", "run", "--host", "0.0.0.0", "--port", "8000"]

##############

&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;PROD&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["flask", "run"]&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We won't get into how to deploy it to production in this article but I wanted to quickly mention that using Gunicorn - a popular WSGI (Web Server Gateway Interface) HTTP server for Python web applications is probably a good idea:&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;# Run the app with Gunicorn &lt;/span&gt;
CMD &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"gunicorn"&lt;/span&gt;, &lt;span class="s2"&gt;"--bind"&lt;/span&gt;, &lt;span class="s2"&gt;"0.0.0.0:8001"&lt;/span&gt;, &lt;span class="s2"&gt;"our_app:app"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  mongo db
&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;mongo_db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mongo_db_${ENV}&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;mongo:4.4.18&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;always&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="s"&gt;27017:27017&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;./DB-STORAGE/mongo-db-$ENV:/data/db&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./db/init-mongo.sh:/docker-entrypoint-initdb.d/init-mongo.sh&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;my_network&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.env&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;container_name&lt;/code&gt; well, we already know :)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;image&lt;/code&gt; specifies the Docker image to be used for the container. In this case, it's using the official MongoDB image with version &lt;code&gt;4.4.18&lt;/code&gt; from Docker Hub&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;restart&lt;/code&gt; indicates the restart policy for the container - &lt;code&gt;always&lt;/code&gt; means the container will be automatically restarted if it exits, regardless of the exit status&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ports&lt;/code&gt; maps port &lt;code&gt;27017&lt;/code&gt; from the host to port &lt;code&gt;27017&lt;/code&gt; in the container (default MongoDB port)

&lt;ul&gt;
&lt;li&gt;Usually, it's best to keep our database private and only let other services (containers) access it. But for development purposes, we can expose it and use tools like MongoDB Compass&lt;/li&gt;
&lt;/ul&gt;


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

&lt;p&gt;&lt;code&gt;volumes&lt;/code&gt; mounts directories or files from the host into the container. This is used to persist data and configuration files&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;./DB-STORAGE/mongo-db-$ENV:/data/db&lt;/code&gt; will mount a host directory named &lt;code&gt;./DB-STORAGE/mongo-db-$ENV&lt;/code&gt; into the &lt;code&gt;/data/db&lt;/code&gt; directory inside the container. This allows the MongoDB data to be stored persistently on the host filesystem&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;./db/init-mongo.sh:/docker-entrypoint-initdb.d/init-mongo.sh&lt;/code&gt; will mounts the &lt;code&gt;init-mongo.sh&lt;/code&gt; file from the host into the &lt;code&gt;/docker-entrypoint-initdb.d/init-mongo.sh&lt;/code&gt; path in the container which is the official MongoDB Docker image entry point.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;networks&lt;/code&gt; attaches the service to a pre-existing Docker network called "my_network". This allows the MongoDB service and other services connected to "my_network" to communicate with each other.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;env_file&lt;/code&gt; the environment variables from &lt;code&gt;.env&lt;/code&gt; file will be used in the entrypoint script (&lt;code&gt;init-mongo.sh&lt;/code&gt;) to configure MongoDB settings during startup&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the &lt;code&gt;init-mongo.sh&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mongo &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MONGO_INITDB_ROOT_USERNAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MONGO_INITDB_ROOT_PASSWORD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--authenticationDatabase&lt;/span&gt; admin &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;

use my_db;

db.createUser({
    user: "&lt;/span&gt;&lt;span class="nv"&gt;$MONGO_USER&lt;/span&gt;&lt;span class="sh"&gt;",
    pwd: "&lt;/span&gt;&lt;span class="nv"&gt;$MONGO_PASSWORD&lt;/span&gt;&lt;span class="sh"&gt;",
    roles: 
        [
            { 
                role: "readWrite", 
                db: "my_db" 
            },
            { 
                role: "dbAdmin", 
                db: "my_db" 
            }     
        ] 
});
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;If we want our MongoDB user to have both read/write access to an existing database and the ability to create collections and documents, we should grant it both the &lt;code&gt;readWrite&lt;/code&gt; role and the &lt;code&gt;dbAdmin&lt;/code&gt; role.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  schedule
&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;schedule_service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;schedule_service_${ENV}&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./schedule&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;./common:/app/common&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./db:/app/db&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;my_network&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;python"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-u"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;schedule.py"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt; 
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.env&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;context&lt;/code&gt; parameter defines the build context, which means the &lt;em&gt;./schedule&lt;/em&gt; directory and its subdirectories will be used to find the necessary files for the build&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;When we're only using a Dockerfile, we can specify the Dockerfile's location and the context (root directory) but when we're working with Docker Compose, we can only specify the context, which means we cannot go up in the directory from where the Dockerfile is&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;docker build -f $RelativePathToSourceCode -t $AppName .&lt;/code&gt; - in that case &lt;code&gt;-f $RelativePathToSourceCode&lt;/code&gt; defines where our &lt;code&gt;Dockerfile&lt;/code&gt; is but the &lt;code&gt;.&lt;/code&gt; at the end defines the context (root directory)&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;In this case, we still want to use some common packages from the application, so we can map them using &lt;code&gt;volumes&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;We already know that &lt;code&gt;command&lt;/code&gt; overrides the default command that would be executed when the container starts. In this case, the script will be executed with the Python interpreter in unbuffered mode (&lt;code&gt;-u&lt;/code&gt; flag) to ensure that the output is immediately displayed in the container logs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In summary, the &lt;code&gt;schedule_service&lt;/code&gt; service in the Docker Compose file builds a container from the &lt;code&gt;./schedule&lt;/code&gt; directory, mounts specific directories from the host into the container, and runs the Python script &lt;code&gt;schedule.py&lt;/code&gt; as the main command for the container. Additionally, it connects the container to the &lt;code&gt;my_network&lt;/code&gt; for communication with other services on the same network and reads environment variables from the &lt;code&gt;.env&lt;/code&gt; file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Firing it all up
&lt;/h2&gt;

&lt;p&gt;Now that we have everything set up, it's time to fire it all up.&lt;/p&gt;

&lt;p&gt;Running &lt;code&gt;docker-compose build --no-cache&lt;/code&gt; followed by &lt;code&gt;docker-compose up&lt;/code&gt; is the best way to ensure that our Docker containers are built from scratch without using any cached layers from previous builds.&lt;/p&gt;

&lt;p&gt;Alternatively, using &lt;code&gt;docker-compose up --build&lt;/code&gt; will rebuild the images for all services, ignoring any previously cached layers. This ensures that we have the latest version of our application and all its dependencies.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;docker-compose down&lt;/code&gt; command both stops and removes containers associated with our Docker Compose project, but the exact behavior depends on the options used with the command.&lt;/p&gt;

&lt;p&gt;By default, &lt;code&gt;docker-compose down&lt;/code&gt; does the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stops all the containers defined in our &lt;code&gt;docker-compose.yml&lt;/code&gt; file that are currently running as part of the project. The containers are gracefully stopped, and their resources are released&lt;/li&gt;
&lt;li&gt;After stopping the containers, &lt;code&gt;docker-compose down&lt;/code&gt; also removes the containers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To clean it all up, we can use &lt;code&gt;docker-compose down --rmi all&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>flask</category>
      <category>python</category>
    </item>
    <item>
      <title>Introdução ao Pelican</title>
      <dc:creator>Felipe Arcaro</dc:creator>
      <pubDate>Sat, 28 Oct 2023 13:14:27 +0000</pubDate>
      <link>https://dev.to/felipearcaro/introducao-ao-pelican-2k6b</link>
      <guid>https://dev.to/felipearcaro/introducao-ao-pelican-2k6b</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Para criar um site estático com Pelican, siga os passos a seguir:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Crie uma pasta do projeto&lt;/li&gt;
&lt;li&gt;Instale o Pelican e os pacotes Markdown executando &lt;code&gt;pip install "pelican[markdown]"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Execute &lt;code&gt;pelican-quickstart&lt;/code&gt; e responda às perguntas que aparecerão&lt;/li&gt;
&lt;li&gt;Crie um artigo chamado &lt;code&gt;meu_artigo.md&lt;/code&gt; com o seguinte conteúdo:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Title: This is my first article
Date: 2010-12-03 10:20
Category: Cool

Cool content goes here.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Execute &lt;code&gt;pelican -r -l&lt;/code&gt;, abra um navegador e acesse &lt;a href="http://localhost:8000/"&gt;http://localhost:8000/&lt;/a&gt; para ver o site funcionando localmente.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  O que é Pelican?
&lt;/h1&gt;

&lt;p&gt;O Pelican é um gerador de site estático escrito em Python.&lt;/p&gt;

&lt;p&gt;Aqui estão algumas coisas que gosto sobre o Pelican:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Podemos escrever conteúdo nos formatos &lt;a href="http://docutils.sourceforge.net/rst.html"&gt;reStructuredText&lt;/a&gt; ou &lt;a href="https://daringfireball.net/projects/markdown/"&gt;Markdown&lt;/a&gt; usando a ferramenta com a qual estamos familiarizados (eu uso o Obsidian, a propósito)&lt;/li&gt;
&lt;li&gt;Ele inclui uma ferramenta de linha de comando simples para gerar nosso site e realizar algumas outras coisas interessantes&lt;/li&gt;
&lt;li&gt;Ele gera um site estático que pode ser facilmente hospedado em qualquer lugar (até mesmo no AWS S3 (Simple Storage Service)!)&lt;/li&gt;
&lt;li&gt;Possui temas e plugins de código aberto feitos pela comunidade &lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  O que é um site estático?
&lt;/h1&gt;

&lt;p&gt;Ok, mas o que é um site estático?&lt;/p&gt;

&lt;p&gt;Um site estático é um tipo de site que não altera seu conteúdo - não se atualiza com base em interações do usuário ou dados em tempo real (sem bancos de dados!). Geralmente é composto por arquivos HTML, CSS e, às vezes, arquivos JavaScript.&lt;/p&gt;

&lt;p&gt;Para pessoas não técnicas, um site estático pode ser entendido como um folheto impresso. É como uma imagem de informações que não muda a menos que alguém fisicamente altere as palavras ou imagens no papel (o programador, no caso).&lt;/p&gt;

&lt;p&gt;Por ser tão simples, normalmente é mais rápido, mais confiável, seguro e com certeza divertido de construir.&lt;/p&gt;

&lt;h1&gt;
  
  
  Por que Pelican?
&lt;/h1&gt;

&lt;p&gt;Sempre tive dificuldades para escolher a melhor ferramenta de escrita. Experimentei o MailChimp, o Medium, o Ulysses, o Evernote, o Notion, o Google Docs e outros, e muitas vezes me vi alternando entre eles - acabando por gastar mais tempo fazendo isso do que escrevendo efetivamente.&lt;/p&gt;

&lt;p&gt;Outra coisa com a qual tive dificuldade foi decidir a melhor maneira de salvar e possivelmente controlar versões das minhas notas, porque, no final do dia, todas essas outras ferramentas ainda seriam responsáveis por armazenar meus dados.&lt;/p&gt;

&lt;p&gt;Decidi escrever minhas notas e artigos como arquivos Markdown (uso o Obsidian para isso) e armazenar esses arquivos automaticamente com o iCloud no Mac.&lt;/p&gt;

&lt;p&gt;E quando termino um artigo ou preciso editar um artigo que já publiquei, posso simplesmente usar o Pelican para (re)gerar as páginas do blog a partir dos meus arquivos Markdown.&lt;/p&gt;

&lt;h1&gt;
  
  
  Configurando o Pelican
&lt;/h1&gt;

&lt;p&gt;Como mencionei anteriormente, o Pelican é construído em Python, portanto, precisamos apenas instalá-lo como um pacote no PyPI (Python Package Index) e executar alguns comandos de linha de comando (CLI). Aqui estão os passos que precisamos seguir:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Crie uma pasta de projeto&lt;/li&gt;
&lt;li&gt;Instale os pacotes Pelican e Markdown (recomendado fazer isso em um ambiente virtual Python)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Crie um ambiente virtual para o Python&lt;/span&gt;
python &lt;span class="nt"&gt;-m&lt;/span&gt; venv env-pelican

&lt;span class="c"&gt;# Ative o ambiente virtual no Linux/Mac&lt;/span&gt;
&lt;span class="nb"&gt;source &lt;/span&gt;env-pelican/bin/activate

&lt;span class="c"&gt;# Ative o ambiente virtual no Windows&lt;/span&gt;
./env-pelican/Scripts/activate

&lt;span class="c"&gt;# Instale o pacote do Pelican&lt;/span&gt;
python &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="s2"&gt;"pelican[markdown]"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Execute &lt;code&gt;pelican-quickstart&lt;/code&gt; e responda às perguntas que aparecerão&lt;/li&gt;
&lt;li&gt;Crie um artigo chamado &lt;code&gt;meu_artigo.md&lt;/code&gt; com o seguinte conteúdo (você pode aprender mais sobre os metadados suportados pelo Pelican &lt;a href="https://docs.getpelican.com/en/latest/content.html"&gt;aqui&lt;/a&gt;)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Title: This is my first article
Date: 2010-12-03 10:20
Category: Cool

Cool content goes here.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Execute &lt;code&gt;pelican -r -l&lt;/code&gt;, abra um navegador e acesse &lt;a href="http://localhost:8000/"&gt;http://localhost:8000/&lt;/a&gt; para ver o site em funcionamento localmente

&lt;ul&gt;
&lt;li&gt;A opção &lt;code&gt;-r&lt;/code&gt; reinicializa o Pelican sempre que uma modificação ocorre nos arquivos de conteúdo&lt;/li&gt;
&lt;li&gt;A opção &lt;code&gt;-l&lt;/code&gt; serve os arquivos de conteúdo via HTTP na porta 8000&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Você vai ver algo muito parecido com a imagem abaixo:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jU09P7m5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://felipearcaro.github.io/images/pelican-website.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jU09P7m5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://felipearcaro.github.io/images/pelican-website.png" alt="pelican-website" width="800" height="590"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>staticwebsite</category>
      <category>pelican</category>
    </item>
    <item>
      <title>Contribuindo para projetos open source</title>
      <dc:creator>Felipe Arcaro</dc:creator>
      <pubDate>Mon, 16 Oct 2023 19:26:41 +0000</pubDate>
      <link>https://dev.to/felipearcaro/contribuindo-para-projetos-open-source-gj0</link>
      <guid>https://dev.to/felipearcaro/contribuindo-para-projetos-open-source-gj0</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Finalmente adentrei o mundo do open source&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/aws/aws-mwaa-local-runner/pull/280"&gt;Airflow - Dynamic DAGs example to MWAA local&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Sempre fui apaixonado pelo mundo do código aberto (open source). A maior parte do meu trabalho atual depende muito de ferramentas e pacotes open source. Na verdade, devemos reconhecer que uma parte significativa da internet funciona em cima do open source.&lt;/p&gt;

&lt;p&gt;No entanto, devo dizer que nunca compreendi verdadeiramente porque as pessoas investiriam inúmeras horas na construção de software gratuito apenas pelo bem da criação - isso é, até eu construir meu primeiro site.&lt;/p&gt;

&lt;p&gt;A sensação que tive ao construir algo do nada foi incrível, mesmo que fosse mal projetado e ninguém fosse usá-lo. Na verdade, às vezes acho o processo de lançar um produto com usuários reais tão divertido quanto criar algo que resolve exclusivamente um problema para mim e talvez para alguns dos meus amigos.&lt;/p&gt;

&lt;p&gt;Nunca me considerei um engenheiro com todas as skills necessárias para contribuir para um projeto open source, no entanto, e até mesmo navegar pelos repositórios no GitHub era assustador.&lt;/p&gt;

&lt;p&gt;Este mês, eu decidi mudar isso. Estou orgulhoso em apresentar minha primeira contribuição open source:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/aws/aws-mwaa-local-runner/pull/280"&gt;Airflow Dynamic DAGs example to MWAA local&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Embora essa contribuição não seja uma proeza de genialidade em engenharia, e uma parte significativa do código já existisse em outro lugar, espero que isso ajude alguém por aí - ou, no mínimo, confunda alguns robôs espreitando nas profundezas da internet.&lt;/p&gt;

</description>
      <category>python</category>
      <category>airflow</category>
      <category>opensource</category>
      <category>data</category>
    </item>
    <item>
      <title>Creating an AWS IAM user with Terraform</title>
      <dc:creator>Felipe Arcaro</dc:creator>
      <pubDate>Tue, 10 Oct 2023 10:06:46 +0000</pubDate>
      <link>https://dev.to/felipearcaro/creating-an-aws-iam-user-with-terraform-49pi</link>
      <guid>https://dev.to/felipearcaro/creating-an-aws-iam-user-with-terraform-49pi</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Here's the Terraform code to create an IAM user with access to EC2 and RDS instances:&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_iam_policy_document"&lt;/span&gt; &lt;span class="s2"&gt;"my_project_policy"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;statement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;resources&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_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;my_project_ec2&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="nx"&gt;actions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ec2:*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="nx"&gt;effect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt; &lt;span class="c1"&gt;# Default is Allow&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;statement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;resources&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"aws_db_instance.my_project_db.arn"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="nx"&gt;actions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"rds:DescribeDBInstances"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                   &lt;span class="s2"&gt;"rds:DescribeDBClusters"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                   &lt;span class="s2"&gt;"rds:DescribeGlobalClusters"&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="c1"&gt;# Provides an IAM user&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_user"&lt;/span&gt; &lt;span class="s2"&gt;"my_project_user"&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;"my-project-user"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Now that we already created an IAM role so the EC2 instance can access the RDS instance, it's time to create an IAM user with specific (and restricted) access to use/manage the infrastructure we just created.&lt;/p&gt;

&lt;p&gt;Here's a quick recap on IAM:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Identity and Access Management (IAM) is a service that allows you to manage access to AWS resources securely&lt;/li&gt;
&lt;li&gt;IAM enables you to create and manage AWS users and groups and control their level of access to AWS resources&lt;/li&gt;
&lt;li&gt;We create groups with permissions and then add users to these groups &lt;/li&gt;
&lt;li&gt;We create roles and assign them to AWS services so they communicate with each other &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Creating an IAM user
&lt;/h2&gt;

&lt;p&gt;Here are the things we'll need to add to our Terraform code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A policy defining permissions&lt;/li&gt;
&lt;li&gt;An IAM user&lt;/li&gt;
&lt;li&gt;Combine the two above&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's start creating the policy:&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_iam_policy_document"&lt;/span&gt; &lt;span class="s2"&gt;"my_project_policy"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;statement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;resources&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_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;my_project_ec2&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="nx"&gt;actions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ec2:*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="nx"&gt;effect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt; &lt;span class="c1"&gt;# Default is Allow&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;statement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;resources&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"aws_db_instance.my_project_db.arn"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="nx"&gt;actions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"rds:DescribeDBInstances"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                   &lt;span class="s2"&gt;"rds:DescribeDBClusters"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                   &lt;span class="s2"&gt;"rds:DescribeGlobalClusters"&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="c1"&gt;# Provides an IAM user&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_user"&lt;/span&gt; &lt;span class="s2"&gt;"my_project_user"&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;"my-project-user"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Provides an IAM policy attached to a user&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_user_policy"&lt;/span&gt; &lt;span class="s2"&gt;"my_project_user_policy"&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;"my-project-policy"&lt;/span&gt;
    &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;my_project_user&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;policy&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_iam_policy_document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;my_project_user_policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You probably noticed this is the first time we're using a &lt;code&gt;data&lt;/code&gt; block instead of a &lt;code&gt;resource&lt;/code&gt; block. The key difference here is that resource blocks are used to create/manage infrastructure resources, while data blocks are used to fetch and reference information from external services.&lt;/p&gt;

&lt;p&gt;Now we create the user:&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;# Provides an IAM user&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_user"&lt;/span&gt; &lt;span class="s2"&gt;"my_project_user"&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;"my-project-user"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And attach the policy to the user:&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;# Provides an IAM policy attached to a user&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_user_policy"&lt;/span&gt; &lt;span class="s2"&gt;"my_project_user_policy"&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;"my-project-policy"&lt;/span&gt;
    &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;my_project_user&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;policy&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_iam_policy_document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;my_project_user_policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;That's it, now we're really done with our little project. After going through all those steps, here's what we've got:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An EC2 instance where we can deploy our Python project&lt;/li&gt;
&lt;li&gt;An RDS Postgres instance where that Python project can persist data&lt;/li&gt;
&lt;li&gt;An IAM role that allows the EC2 instance to connect to the RDS Postgres instance&lt;/li&gt;
&lt;li&gt;An IAM user with proper permissions to interact with the EC2 and RDS instances&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>terraform</category>
      <category>dataops</category>
      <category>devops</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Creating an AWS IAM role with Terraform</title>
      <dc:creator>Felipe Arcaro</dc:creator>
      <pubDate>Tue, 03 Oct 2023 19:44:04 +0000</pubDate>
      <link>https://dev.to/felipearcaro/creating-an-aws-iam-role-with-terraform-1al8</link>
      <guid>https://dev.to/felipearcaro/creating-an-aws-iam-role-with-terraform-1al8</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Here's the Terraform code to create an IAM role so an EC2 instance has proper access to an RDS instance:&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;# IAM role for EC2 instance&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_role"&lt;/span&gt; &lt;span class="s2"&gt;"my_project_role"&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;"my-project-role"&lt;/span&gt;

  &lt;span class="nx"&gt;assume_role_policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;({&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;"2012-10-17"&lt;/span&gt;
    &lt;span class="nx"&gt;Statement&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;Action&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sts:AssumeRole"&lt;/span&gt;
        &lt;span class="nx"&gt;Effect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;
        &lt;span class="nx"&gt;Sid&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
        &lt;span class="nx"&gt;Principal&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&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;"ec2.amazonaws.com"&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="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# IAM policy to be attached to the EC2 role&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_role_policy"&lt;/span&gt; &lt;span class="s2"&gt;"my_project_role_policy"&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;"my-project-role-policy"&lt;/span&gt;
  &lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;my_project_role&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;policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;({&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;"2012-10-17"&lt;/span&gt;
    &lt;span class="nx"&gt;Statement&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;Action&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="s2"&gt;"rds:*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="nx"&gt;Effect&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;
        &lt;span class="nx"&gt;Resource&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;my_project_db&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="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Update EC2 resource created earlier&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_project_ec2"&lt;/span&gt; &lt;span class="p"&gt;{&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;"t2.micro"&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-06e46074ae430fba6"&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;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-python-project"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;iam_instance_profile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;my_project_role&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Nice, we're done with the goals we defined at the beginning of this article but we want more – actually, we need more. Now it's time to create an IAM user with specific (and restricted) access to use/manage the infrastructure we just created and an IAM role so the EC2 instance can access the RDS instance.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Identity and Access Management (IAM) is a service that allows you to manage access to AWS resources securely. IAM enables you to create and manage AWS users and groups and control their level of access to AWS resources.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As per IAM standards:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We create groups with permissions and then add users to these groups &lt;/li&gt;
&lt;li&gt;We create roles and assign them to AWS services so they communicate with each other &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this example, we won't create any IAM groups but it is important to remember that we cannot assign a role to a user.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating an IAM role
&lt;/h2&gt;

&lt;p&gt;Here are the things we'll need to add to our Terraform code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A policy defining permissions&lt;/li&gt;
&lt;li&gt;An IAM role&lt;/li&gt;
&lt;li&gt;Update the EC2 code to use that role&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's start creating an IAM role for our EC2 service:&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;# IAM role for EC2 instance&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_role"&lt;/span&gt; &lt;span class="s2"&gt;"my_project_role"&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;"my-project-role"&lt;/span&gt;

  &lt;span class="nx"&gt;assume_role_policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;({&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;"2012-10-17"&lt;/span&gt;
    &lt;span class="nx"&gt;Statement&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;Action&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sts:AssumeRole"&lt;/span&gt;
        &lt;span class="nx"&gt;Effect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;
        &lt;span class="nx"&gt;Sid&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
        &lt;span class="nx"&gt;Principal&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&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;"ec2.amazonaws.com"&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now we create a policy with all permissions to our RDS instance and reference the role we just created.&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;# IAM policy to be attached to the EC2 role&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_role_policy"&lt;/span&gt; &lt;span class="s2"&gt;"my_project_role_policy"&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;"my-project-role-policy"&lt;/span&gt;
  &lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;my_project_role&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;policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;({&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;"2012-10-17"&lt;/span&gt;
    &lt;span class="nx"&gt;Statement&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;Action&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="s2"&gt;"rds:*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="nx"&gt;Effect&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;
        &lt;span class="nx"&gt;Resource&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;my_project_db&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="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;Now we have to update our EC2 instance code to make sure it uses the role and permissions we just defined. For that, we just add &lt;code&gt;iam_instance_profile = aws_iam_role.my_project_role.arn&lt;/code&gt; to it:&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;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"my_project_ec2"&lt;/span&gt; &lt;span class="p"&gt;{&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;"t2.micro"&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-06e46074ae430fba6"&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;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-python-project"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;iam_instance_profile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;my_project_role&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>terraform</category>
      <category>dataops</category>
      <category>devops</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Creating an AWS EC2 instance with Terraform</title>
      <dc:creator>Felipe Arcaro</dc:creator>
      <pubDate>Mon, 25 Sep 2023 16:48:14 +0000</pubDate>
      <link>https://dev.to/felipearcaro/creating-an-aws-ec2-instance-with-terraform-1i3k</link>
      <guid>https://dev.to/felipearcaro/creating-an-aws-ec2-instance-with-terraform-1i3k</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Here's the Terraform code to create an EC2 instance:&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;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"my_project_ec2"&lt;/span&gt; &lt;span class="p"&gt;{&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;"t2.micro"&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-06e46074ae430fba6"&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;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-python-project"&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;h2&gt;
  
  
  EC2 Instance (Elastic Compute Cloud)
&lt;/h2&gt;

&lt;p&gt;Elastic Compute Cloud (EC2) is a virtual server provided by AWS that allows you to run applications and services in the cloud - it's just a regular computer but in the cloud.&lt;/p&gt;

&lt;p&gt;EC2 is awesome because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We don't need to invest in infrastructure upfront&lt;/li&gt;
&lt;li&gt;It allows us to scale up/down (vertically) to handle different workloads&lt;/li&gt;
&lt;li&gt;It offers different purchasing options (on demand, reserved, spot)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Terraform it up
&lt;/h3&gt;

&lt;p&gt;We're past the basics now, so let's create an EC2 instance. Here's the code to do it:&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;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"my_project_ec2"&lt;/span&gt; &lt;span class="p"&gt;{&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;"t2.micro"&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-06e46074ae430fba6"&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;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-python-project"&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;blockquote&gt;
&lt;p&gt;The resource name &lt;code&gt;my-python-project&lt;/code&gt; that comes after the resource type &lt;code&gt;aws_instance&lt;/code&gt; is not the same as the resource name that appears in the Admin Console. For that, I had to use &lt;code&gt;tags&lt;/code&gt; parameter.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once again, the infrastructure is updated after we run &lt;code&gt;terraform plan&lt;/code&gt; and &lt;code&gt;terraform apply&lt;/code&gt; and a new EC2 instance is created.i&lt;/p&gt;

&lt;h3&gt;
  
  
  Wait, what is AMI?
&lt;/h3&gt;

&lt;p&gt;Amazon Machine Image (AMI) is a pre-configured package required to launch an EC2 instance that includes an operating system, software packages and other requirements. It's essentially a template that can be used to launch EC2 instances (Linux, Windows or Mac)&lt;/p&gt;

&lt;p&gt;AMI is awesome because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We don't need to manually install packages and requirements&lt;/li&gt;
&lt;li&gt;We can launch several EC2 with the same configuration (load balancing)&lt;/li&gt;
&lt;li&gt;When selecting the AMI, we can use the Advanced Details tab to run a bash script (e.g. to update &lt;code&gt;yum&lt;/code&gt; and install &lt;code&gt;httpd&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ok. So far, we have a computer where we can run our project and a database in which we can persist and read data. Now it's time to give them proper access - in other words, we have to specify what AWS resources and who should be able to access them.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>dataops</category>
      <category>devops</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Creating an AWS RDS instance with Terraform</title>
      <dc:creator>Felipe Arcaro</dc:creator>
      <pubDate>Mon, 18 Sep 2023 18:07:58 +0000</pubDate>
      <link>https://dev.to/felipearcaro/creating-an-aws-rds-instance-with-terraform-oba</link>
      <guid>https://dev.to/felipearcaro/creating-an-aws-rds-instance-with-terraform-oba</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Here's the Terraform code to create an RDS Postgres instance:&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;# RDS instance&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_db_instance"&lt;/span&gt; &lt;span class="s2"&gt;"my_project_db"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;engine&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"postgres"&lt;/span&gt;
  &lt;span class="nx"&gt;instance_class&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="nx"&gt;username&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sidewalk"&lt;/span&gt;
  &lt;span class="nx"&gt;password&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="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postgres_pw&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&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;20&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;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Creating an RDS instance
&lt;/h2&gt;

&lt;p&gt;Amazon Relational Database Service (RDS) is a managed database service provided by AWS that simplifies the process of setting up, operating, and scaling a relational database in the cloud.&lt;/p&gt;

&lt;p&gt;Here is a quick recap of some RDS notable features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multi-AZ

&lt;ul&gt;
&lt;li&gt;Supports standby instance in another AZ that helps with failure recover &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Read replicas

&lt;ul&gt;
&lt;li&gt;Offload read requests&lt;/li&gt;
&lt;li&gt;Can be used in disaster recovery plan&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Automatic backups

&lt;ul&gt;
&lt;li&gt;Configurable time window&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Configurable storage &lt;/li&gt;
&lt;li&gt;IAM authentication&lt;/li&gt;
&lt;li&gt;RDS Enhance monitoring &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Starting in the Management Console (UI)
&lt;/h3&gt;

&lt;p&gt;First, we create a free RDS Postgres instance through the Management Console. Since this was the first AWS service on this account, it automatically created a VPC &lt;sup id="fnref1"&gt;1&lt;/sup&gt; and a Security Group &lt;sup id="fnref2"&gt;2&lt;/sup&gt; to ensure the instance was securely isolated and protected by a firewall.&lt;/p&gt;

&lt;p&gt;To see if the Postgres database is working, we can use PGAdmin, but we also need to go through the following steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make the RDS instance publicly available&lt;/li&gt;
&lt;li&gt;Add our personal IP to the Security Group's inbound rules used by the database so we can connect to it from outside the VPC (our personal computer):

&lt;ul&gt;
&lt;li&gt;Although not recommended, we can all ports &lt;code&gt;0-655353&lt;/code&gt; and all IPs &lt;code&gt;0.0.0.0/0&lt;/code&gt; to the inbound rules so everyone can connect to our database&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything seems to be working as expected. &lt;/p&gt;

&lt;h3&gt;
  
  
  Importing existing resources to Terraform code
&lt;/h3&gt;

&lt;p&gt;Now, let's start with &lt;code&gt;terraform import&lt;/code&gt; to import my database instance to my configuration file so I can manage it from there.&lt;/p&gt;

&lt;p&gt;First, we create the most basic version of the &lt;code&gt;aws_db_instance&lt;/code&gt; resource on our Terraform &lt;code&gt;main.tf&lt;/code&gt; file:&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;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_db_instance"&lt;/span&gt; &lt;span class="s2"&gt;"my_project_db"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;engine&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"postgres"&lt;/span&gt;
  &lt;span class="nx"&gt;instance_class&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"db.t2.micro"&lt;/span&gt;
  &lt;span class="nx"&gt;username&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sidewalk"&lt;/span&gt;
  &lt;span class="nx"&gt;password&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="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postgres_pw&lt;/span&gt;&lt;span class="k"&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;You probably noticed we're using a variable instead of writing the password in plain text. Here's the easiest way to do it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a file called &lt;code&gt;vars.tf&lt;/code&gt; in the same folder as the &lt;code&gt;main.tf&lt;/code&gt; file and add the following code to it:
&lt;/li&gt;
&lt;/ul&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;"super_secret_password"&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="s2"&gt;"ThisPassword123!"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can call it from your main terraform code like this &lt;code&gt;${var.super_secret_password}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Ok, now it's time to run &lt;code&gt;terraform import aws_instance.my_project_db &amp;lt;db-instance-id&amp;gt;&lt;/code&gt; (you can find the &lt;code&gt;&amp;lt;db-instance-id&amp;gt;&lt;/code&gt; in the Management Console) – it seems like the database was imported successfully.&lt;/p&gt;

&lt;p&gt;There was a small problem though – the database we created through the Management Console had the parameter &lt;code&gt;instance_class = db.t3.micro&lt;/code&gt; while our Terraform code had &lt;code&gt;instance_class = db.t2.micro&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;When we run &lt;code&gt;terraform plan&lt;/code&gt;, it says that a particular resource &lt;em&gt;must be replaced&lt;/em&gt;. Then, when we run &lt;code&gt;terraform apply&lt;/code&gt;, the database created through the Management Console is deleted and we get an error saying there were some important parameters missing to create the new one – in this case, the &lt;code&gt;allocated_storage&lt;/code&gt; parameter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key learnings on using &lt;code&gt;terraform import&lt;/code&gt;:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;To import an existing infrastructure from AWS, you must have the resource in the Terraform code with all required parameters&lt;/li&gt;
&lt;li&gt;Make sure the configuration you have on your configuration file matches exactly what you have on AWS&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform plan&lt;/code&gt; won't show if there are required parameters missing, only &lt;code&gt;terraform apply&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Creating resources from Terraform code
&lt;/h3&gt;

&lt;p&gt;Now that we know how this works, let's go ahead and create the database directly from the Terraform code using &lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_instance"&gt;AWS documentation&lt;/a&gt; on &lt;code&gt;aws_db_instance&lt;/code&gt;:&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;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_db_instance"&lt;/span&gt; &lt;span class="s2"&gt;"my_project_db"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;engine&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"postgres"&lt;/span&gt;
  &lt;span class="nx"&gt;instance_class&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="nx"&gt;username&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sidewalk"&lt;/span&gt;
  &lt;span class="nx"&gt;password&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="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postgres_pw&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&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;20&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can run &lt;code&gt;terraform plan&lt;/code&gt; to make sure everything is looking good and &lt;code&gt;terraform apply&lt;/code&gt; to create the database. After the database is created, we can type &lt;code&gt;terraform show&lt;/code&gt; to see the infrastructure's current state – the VPC, the Security Group, etc.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key learnings on creating infrastructure using Terraform code:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;It automatically created it on the only VPC available on that AWS account&lt;/li&gt;
&lt;li&gt;It automatically added the only Security Group available on that AWS account&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And here's the updated Terraform code to make the RDS instance publicly accessible:&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;# RDS instance&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_db_instance"&lt;/span&gt; &lt;span class="s2"&gt;"my_project_db"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;engine&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"postgres"&lt;/span&gt;
  &lt;span class="nx"&gt;instance_class&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="nx"&gt;username&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sidewalk"&lt;/span&gt;
  &lt;span class="nx"&gt;password&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="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postgres_pw&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&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;20&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;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;VPC – Virtual Private Cloud is a virtual network infrastructure that allows you to create a logically isolated network environment. It is essentially a virtual data center in the cloud ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;A Security Group is a virtual firewall that controls the inbound and outbound traffic for one or more instances. Every instance within a VPC in AWS must be associated with at least one security group. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>terraform</category>
      <category>dataops</category>
      <category>devops</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Introduction to Terraform</title>
      <dc:creator>Felipe Arcaro</dc:creator>
      <pubDate>Tue, 12 Sep 2023 23:17:00 +0000</pubDate>
      <link>https://dev.to/felipearcaro/introduction-to-terraform-4jfn</link>
      <guid>https://dev.to/felipearcaro/introduction-to-terraform-4jfn</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Terraform is a tool used in the field of infrastructure as code (IaC) to create and manage computer resources – servers, networks, databases, and so on. It allows us to define a desired infrastructure in a configuration file using a simple and declarative language.&lt;/p&gt;

&lt;p&gt;Once a Terraform project is started, the state of the managed infrastructure and configuration needs to be stored somewhere - usually locally or on AWS S3. Then, that state file is used by Terraform essentially to map real-world resources to our configuration file and keep track of metadata.&lt;/p&gt;

&lt;p&gt;Here are some important Terraform commands to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;terraform plan&lt;/code&gt;: Generates an execution plan, showing changes to infrastructure resources that would be made when applied&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform apply&lt;/code&gt;: Applies the changes defined in Terraform configuration to create or update infrastructure resources&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform destroy&lt;/code&gt;: Destroys and removes all resources created by Terraform for a given configuration&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform show&lt;/code&gt;: Displays the current state or details of Terraform-managed resources&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform import&lt;/code&gt;: Imports existing infrastructure resources into Terraform's state for management&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What is Terraform?
&lt;/h2&gt;

&lt;p&gt;Before we jump into coding things out, let's go over some key concepts of Terraform.&lt;/p&gt;

&lt;p&gt;Terraform is a tool used in the field of infrastructure as code (IaC) to create and manage computer resources – servers, networks, databases, and so on. It allows us to define a desired infrastructure in a configuration file using a simple and declarative language.&lt;/p&gt;

&lt;p&gt;Once we have defined our infrastructure configuration, Terraform takes care of provisioning and managing the resources. It interacts with cloud providers like Amazon Web Services (AWS) to create and update infrastructure based on the configuration provided in the file.&lt;/p&gt;

&lt;p&gt;Using Terraform enables us to treat infrastructure as code – we can version control it, collaborate with others, and easily replicate or rebuild it in a consistent and repeatable manner.&lt;/p&gt;

&lt;p&gt;And since we're going to be provisioning infrastructure in AWS, it's also important to understand the difference between a resource, an instance, and a service.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A resource is a logical entity that represents a piece of AWS infrastructure – an S3 bucket, an EC2 instance, an RDS database&lt;/li&gt;
&lt;li&gt;An instance is a specific occurrence of a resource that is created from a particular template or configuration – an  EC2 instance is a virtual server that is launched from an Amazon Machine Image (AMI). Each instance is a unique copy of the AMI, with its own IP address, storage, and other attributes&lt;/li&gt;
&lt;li&gt;A service is a collection of related AWS resources and API operations that work together to perform a specific function – AWS offers over 200 different services, including compute, storage, database, analytics, machine learning, and more&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting the environment ready
&lt;/h2&gt;

&lt;p&gt;There are a few steps we need to go through in order to get our development environment ready: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html"&gt;AWS CLI&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Run &lt;code&gt;aws configure&lt;/code&gt; on the terminal and provide the following parameters

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;AWS Access Key ID&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;&lt;code&gt;AWS Secret Access Key&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Default region name&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Default output format&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Install &lt;a href="https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli"&gt;Terraform CLI&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Project structure
&lt;/h2&gt;

&lt;p&gt;Here's the project structure we are working with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-project/
└── terraform/
    └── main.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here's the boilerplate code we're starting with:&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;# Specify AWS resource&lt;/span&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;source&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;"~&amp;gt; 4.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="c1"&gt;# Configure the AWS Provider&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="s2"&gt;"us-east-1"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Define where the state will be stored&lt;/span&gt;
&lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;backend&lt;/span&gt; &lt;span class="s2"&gt;"local"&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;".tfstate"&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;To start the Terraform project, we navigate to the &lt;em&gt;my-project/terraform/&lt;/em&gt; directory and run &lt;code&gt;terraform init&lt;/code&gt; which essentially downloads all necessary modules and defines the backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wait, what is this backend thing?
&lt;/h2&gt;

&lt;p&gt;To understand what the Terraform backend is, let's quickly cover what a Terraform state is first.&lt;/p&gt;

&lt;p&gt;You probably saw some state-related code in the code snippet I provided earlier:&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;# Define where the state will be stored&lt;/span&gt;
&lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;backend&lt;/span&gt; &lt;span class="s2"&gt;"local"&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;".tfstate"&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;Once a Terraform project is started, the state of the managed infrastructure and configuration needs to be stored somewhere, and that state is used by Terraform essentially to map real-world resources to our configuration file and keep track of metadata.&lt;/p&gt;

&lt;p&gt;Here are some key aspects of the Terraform state:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Its primary purpose is to store bindings between objects in a remote system (e.g. an EC2 instance) and resource instances declared in a configuration file&lt;/li&gt;
&lt;li&gt;It's generally saved in a file called &lt;code&gt;terraform.tfstate&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;It can be stored locally – recommended for developing purposes&lt;/li&gt;
&lt;li&gt;It can be stored in the cloud (e.g S3) – recommended for deployment purposes, so it can be versioned, encrypted, and securely shared&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The backend simply defines where Terraform stores its state.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to "run" terraform code
&lt;/h2&gt;

&lt;p&gt;We already have some Terraform code, can't we just run it? Kinda. Let's take a look at the following commands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;terraform plan&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform apply&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform destroy&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform show&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform import&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Terraform plan
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;terraform plan&lt;/code&gt; command creates a plan. It can be used to preview the planned changes to the infrastructure.&lt;/p&gt;

&lt;p&gt;Under the hood, the command:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reads the current state of any remote objects to make sure that the Terraform state is up-to-date&lt;/li&gt;
&lt;li&gt;Compares the current configuration to the prior state and notes any differences&lt;/li&gt;
&lt;li&gt;Proposes a set of change actions that should, if applied, make the remote objects match the configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Terraform apply
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;terraform plan&lt;/code&gt; command applies the changes to the infrastructure to get to the state where the remote objects match the configuration. &lt;code&gt;terraform apply&lt;/code&gt; can be run without running &lt;code&gt;terraform plan&lt;/code&gt; first although it's not recommended.&lt;/p&gt;

&lt;h3&gt;
  
  
  Terraform destroy
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;terraform destroy&lt;/code&gt; command is used to destroy all mapped objects. &lt;code&gt;terraform plan -destroy&lt;/code&gt; can be used to see what destruction will be made beforehand.&lt;/p&gt;

&lt;p&gt;It is also worth mentioning that if the infrastructure has already been provisioned with &lt;code&gt;terraform apply&lt;/code&gt;, removing objects from the Terraform code and running &lt;code&gt;terraform apply&lt;/code&gt; again will also destroy them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Terraform show
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;terraform show&lt;/code&gt; command can be used at any time to inspect the configuration of a Terraform state.&lt;/p&gt;

&lt;h3&gt;
  
  
  Terraform import
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;terraform import&lt;/code&gt; command can be used for bringing existing resources under Terraform management.&lt;/p&gt;

&lt;p&gt;With that in our toolkit, it's time to get our hands dirty.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>dataops</category>
      <category>devops</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Contributing to open source projects</title>
      <dc:creator>Felipe Arcaro</dc:creator>
      <pubDate>Wed, 06 Sep 2023 21:11:54 +0000</pubDate>
      <link>https://dev.to/felipearcaro/contributing-to-open-source-projects-59c6</link>
      <guid>https://dev.to/felipearcaro/contributing-to-open-source-projects-59c6</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;I have made my initial foray into the realm of open-source contributions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/aws/aws-mwaa-local-runner/pull/280"&gt;Airflow - Dynamic DAGs example to MWAA local&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;I have always been passionate about the open-source world. The majority of my current job relies heavily on open-source tools and packages. In fact, it is remarkable to acknowledge that a significant portion of the internet operates on open-source code.&lt;/p&gt;

&lt;p&gt;However, I must say that I never truly comprehended why people would invest countless hours in building software merely for the sake of creation – that is, until I built my first website.&lt;/p&gt;

&lt;p&gt;The high I got from building something out of thin air was amazing, even though it was poorly designed and nobody was going to use it. In fact, at times, I find the process of shipping a product with real users to be as fun as creating something that exclusively solves a problem for myself and perhaps for some of my friends.&lt;/p&gt;

&lt;p&gt;I never really considered myself a good-enough engineer to contribute to an open-source project, though, and even browsing through open-source repositories was overwhelming and daunting.&lt;/p&gt;

&lt;p&gt;However, this month, I decided to change that. I am proud to present my inaugural open-source contribution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/aws/aws-mwaa-local-runner/pull/280"&gt;Airflow - Dynamic DAGs example to MWAA local&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While it may not be a feat of engineering genius, and a significant portion of the code already existed elsewhere, hopefully it will help someone out there – or, at the very least, confuse a few robots lurking in the depths of the crazy interwebs.&lt;/p&gt;

</description>
      <category>python</category>
      <category>airflow</category>
      <category>opensource</category>
      <category>data</category>
    </item>
    <item>
      <title>Adding different Pelican themes to your static website</title>
      <dc:creator>Felipe Arcaro</dc:creator>
      <pubDate>Fri, 01 Sep 2023 17:42:26 +0000</pubDate>
      <link>https://dev.to/felipearcaro/adding-different-pelican-themes-to-your-static-website-26ga</link>
      <guid>https://dev.to/felipearcaro/adding-different-pelican-themes-to-your-static-website-26ga</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;In order to add a different theme to your static website generated with Pelican, do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clone one of these &lt;a href="https://github.com/getpelican/pelican-themes"&gt;community-managed theme repos&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Run the following command on the terminal &lt;code&gt;pelican content -t /path/to/theme -r -l&lt;/code&gt; &lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  What is a Pelican theme?
&lt;/h1&gt;

&lt;p&gt;When you create your first static website using Pelican, you'll notice the design looks like it hasn't been updated since the 90s:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jU09P7m5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://felipearcaro.github.io/images/pelican-website.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jU09P7m5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://felipearcaro.github.io/images/pelican-website.png" alt="pelican-website" width="800" height="590"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's exactly where Pelican themes come into play. There is a community-managed repository of &lt;a href="https://github.com/getpelican/pelican-themes"&gt;Pelican Themes&lt;/a&gt; for people to share and use.&lt;/p&gt;

&lt;h1&gt;
  
  
  Can I create my own Pelican theme?
&lt;/h1&gt;

&lt;p&gt;You can obviously &lt;a href="https://docs.getpelican.com/en/latest/themes.html#creating-themes"&gt;create your own Pelican theme&lt;/a&gt; but if you want to get up and running fast with a not-so-bad-looking website, the easiest path is to pick &lt;a href="https://pelicanthemes.com/"&gt;a Pelican theme that already exists&lt;/a&gt;. &lt;/p&gt;

&lt;h1&gt;
  
  
  How to use a Pelican theme
&lt;/h1&gt;

&lt;p&gt;Although it hasn't been updated in 7 years, I really like the simplicity/responsiveness of the Alchemy theme, so that's what we're using for this example.&lt;/p&gt;

&lt;h2&gt;
  
  
  Clone the repository
&lt;/h2&gt;

&lt;p&gt;Navigate to the &lt;a href="https://github.com/nairobilug/pelican-alchemy/tree/f235c81bf323e6134b01915fc9a46b5e89ac238b"&gt;Alchemy Pelican theme repository&lt;/a&gt; and clone it. You'll normally find more information about a theme by looking at the &lt;a href="https://github.com/nairobilug/pelican-alchemy/blob/master/README.md"&gt;README.md file&lt;/a&gt; in the repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  Change your configs
&lt;/h2&gt;

&lt;p&gt;There are two ways you can tell Pelican to use your new theme when creating your static website:&lt;/p&gt;

&lt;p&gt;Reference it with the &lt;code&gt;-t&lt;/code&gt; flag when running your &lt;code&gt;pelican&lt;/code&gt; command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pelican content -t path/to/the/theme/pelican-alchemy/alchemy -r -l
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reference it in the &lt;code&gt;pelicanconf.py&lt;/code&gt; file adding the following line to it&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;THEME = 'path/to/the/theme/pelican-alchemy/alchemy'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then running the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pelican content -s pelicanconf.py path/to/the/theme/pelican-alchemy/alchemy -r -l
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Make sure you're specifying the right path – in this example, we need to specify &lt;code&gt;pelican-alchemy/alchemy&lt;/code&gt; and not just &lt;code&gt;pelican-alchemy&lt;/code&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here's how your static website will look like with the new Alchemy theme:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--W6x116aO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://felipearcaro.github.io/images/pelican-website-alchemy-theme.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--W6x116aO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://felipearcaro.github.io/images/pelican-website-alchemy-theme.png" alt="pelican-website-alchemy-theme" width="800" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>staticwebsite</category>
      <category>pelican</category>
    </item>
  </channel>
</rss>
