<?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: PH Saurav</title>
    <description>The latest articles on DEV Community by PH Saurav (@phsaurav).</description>
    <link>https://dev.to/phsaurav</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%2F1730640%2Fbed440e8-9f74-4b52-b3e9-1effd2f0eeef.png</url>
      <title>DEV Community: PH Saurav</title>
      <link>https://dev.to/phsaurav</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/phsaurav"/>
    <language>en</language>
    <item>
      <title>Docker Networking Demystified: From Bridge to Overlay with Examples</title>
      <dc:creator>PH Saurav</dc:creator>
      <pubDate>Fri, 25 Jul 2025 04:58:13 +0000</pubDate>
      <link>https://dev.to/phsaurav/docker-networking-demystified-from-bridge-to-overlay-with-examples-2go2</link>
      <guid>https://dev.to/phsaurav/docker-networking-demystified-from-bridge-to-overlay-with-examples-2go2</guid>
      <description>&lt;p&gt;Containerization transformed everything from software development to deployment. Docker sits at the center of this transformation. Although Docker is so widely used, its implementation is surrounded by confusion and mystery. &lt;br&gt;
In this article, we will try to demystify Docker networking by exploring key concepts with clear, hands-on examples. We'll break down how these networks function and why they matter.&lt;/p&gt;
&lt;h2&gt;
  
  
  Key Concepts:
&lt;/h2&gt;
&lt;h3&gt;
  
  
  The Foundation:
&lt;/h3&gt;

&lt;p&gt;Although many draw parallels between Docker and Virtual Machines, they miss the foundational architectural difference. The building block of Docker is much different from its main focus on isolation rather than full emulation.  All processes run on the same host OS, isolated through Linux Namespaces and managed by cgroups. Namespaces provide the boundaries, and cgroups manage the resource allocation. For networking, Docker leverages Network Namespaces, which will be our deep dive today. We'll explore the fascinating world of cgroups in a future discussion.&lt;/p&gt;
&lt;h3&gt;
  
  
  Network NameSpaces:
&lt;/h3&gt;

&lt;p&gt;Linux namespaces isolate system resources, and the network namespace is no exception—it creates a completely independent network stack. Each namespace owns its own routing tables, firewall rules,&lt;br&gt;
network interfaces, and even virtual devices, making every container believe it has the entire network to itself.&lt;/p&gt;
&lt;h3&gt;
  
  
  Linux Bridge:
&lt;/h3&gt;

&lt;p&gt;A bridge connects devices at Layer 2 of the OSI model. The connection is made using network interfaces and physical MAC addresses by extending the broadcast domain.&lt;br&gt;
A Linux bridge is the virtual equivalent. It creates an internal Layer 2 bridge that links multiple network namespaces, letting their interfaces communicate as if they were plugged into the same physical bridge.&lt;/p&gt;
&lt;h3&gt;
  
  
  Quick Reference: Core Networking Elements
&lt;/h3&gt;

&lt;p&gt;eth0 (Virtual Ethernet) – The primary network interface on most Linux systems. Containers and host reach the outside world through this interface.&lt;/p&gt;

&lt;p&gt;lo (loopback) – The loopback interface (127.0.0.1/localhost). Traffic sent here stays inside the same namespace. This allows processes on the same host to communicate using networking APIs (using Port), but the data never leaves the system physically.&lt;/p&gt;

&lt;p&gt;MAC Address – Layer-2 hardware address (e.g., 02:42:ac:11:00:02) used by bridges and switches to deliver frames to the correct interface within the same local segment.&lt;/p&gt;

&lt;p&gt;IP Address – Layer-3 identifier (e.g., 172.17.0.2) that lets namespaces locate and talk to each other across networks.&lt;/p&gt;
&lt;h2&gt;
  
  
  Environment Setup:
&lt;/h2&gt;

&lt;p&gt;Practice cements understanding. To follow and try out examples given below, you will just need a Linux machine, physical or VM with Docker installed on it. I am using Ubuntu 20.04 VM running Docker version 28.3.2.&lt;br&gt;&lt;br&gt;
Start by listing every network interface on the host:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ip &lt;span class="nb"&gt;link &lt;/span&gt;list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see something similar to this:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxxj97mqhi67sh8zudict.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxxj97mqhi67sh8zudict.jpg" alt="Hosts Network Interface List" width="800" height="123"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The main interfaces that concern us are the loopback interface(lo) and Primary ethernet interface(eth0). There is another &lt;code&gt;interesting&lt;/code&gt; interface &lt;code&gt;docker0&lt;/code&gt; that we will discuss in detail later when discussing bridge driver.&lt;/p&gt;
&lt;h1&gt;
  
  
  Docker Networking
&lt;/h1&gt;

&lt;p&gt;The networking in Docker is managed by some pluggable drivers. Mainly, there are six of them.  We’ll walk through each driver, then peek under the hood to see how they map to network namespaces in your environment.&lt;/p&gt;
&lt;h2&gt;
  
  
  1. None Network Driver
&lt;/h2&gt;

&lt;p&gt;This completely isolates the container from any outside access. What I mean by that is let's see what happens when we choose &lt;code&gt;none&lt;/code&gt; driver with &lt;code&gt;--network none&lt;/code&gt; flag.&lt;/p&gt;

&lt;p&gt;Let's start a container with an alpine image where the network driver is &lt;code&gt;none&lt;/code&gt; and start a shell in it with this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--network&lt;/span&gt; none alpine sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now run the same command we used previously to list network interfaces on host:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ip &lt;span class="nb"&gt;link &lt;/span&gt;list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;You will see something like this:&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Notice the container only exposes the lo (loopback) interface—eth0 is absent. Without eth0, the container has no path to the outside world; lo alone provides an isolated, localhost-only network that applications inside can use for internal communication.&lt;/p&gt;

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

&lt;p&gt;Exit the container:&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;exit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;WHY &lt;code&gt;none&lt;/code&gt;:&lt;/strong&gt; It's useful when you don't want your container to have any external connectivity in a fully isolated network most probably for security reasons.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Host Network Driver
&lt;/h2&gt;

&lt;p&gt;The host driver removes network isolation. Containers share the host's network stack, including IP addresses, ports, and interfaces.&lt;/p&gt;

&lt;p&gt;To see host network driver in action, let's start a similar image in interactive shell mode with &lt;code&gt;--network host&lt;/code&gt; and list network interfaces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--network&lt;/span&gt; host alpine sh
ip &lt;span class="nb"&gt;link &lt;/span&gt;list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will notice that all the interfaces are exactly same as the host's interface. &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpdsbjrjwtjdnkb6m7vy3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpdsbjrjwtjdnkb6m7vy3.jpg" alt="Host Network Driver Ouput" width="800" height="343"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Exit the container:&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;exit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;WHY &lt;code&gt;host&lt;/code&gt;:&lt;/strong&gt; It's useful when you don't want any isolation and want to optimize maximum bare-metal performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Bridge Network Driver
&lt;/h2&gt;

&lt;p&gt;Finally, we are here with the bridge networking driver that we all use most of the time. As I mentioned above, a bridge is a device that connects devices or network segments at layer 2 and expands the network. &lt;br&gt;
In this network mode, the containers are connected to a virtual Linux bridge. Containers connected to it receive their own IP addresses and can talk to one another freely while still sharing the host’s connection to the outside world.&lt;/p&gt;

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

&lt;p&gt;When someone creates a container without mentioning any network driver type, it creates a default bridge network. Now the question is, I didn't create any bridge. So, where is my container getting connected to? 🤔&lt;/p&gt;

&lt;p&gt;Did you remember when we ran &lt;code&gt;ip link list&lt;/code&gt; on host? There was an interesting interface called &lt;code&gt;docker0&lt;/code&gt;. I told you we will come back to this. This mysterious interface is our default bridge.&lt;/p&gt;

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

&lt;p&gt;Docker creates it by default and any container by default connects to this default bridge.&lt;/p&gt;

&lt;p&gt;Now, to test it out, let's first check out the current status of the bridge. It should be empty if there is no container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ip &lt;span class="nb"&gt;link &lt;/span&gt;show master docker0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's create two containers in the background and check their connection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; container1 alpine &lt;span class="nb"&gt;sleep &lt;/span&gt;3600
&lt;span class="nb"&gt;sudo &lt;/span&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; container2 alpine &lt;span class="nb"&gt;sleep &lt;/span&gt;3600

&lt;span class="nb"&gt;sudo &lt;/span&gt;docker &lt;span class="nb"&gt;exec &lt;/span&gt;container1 ip addr &lt;span class="c"&gt;# Take the eth0 ip address&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;docker &lt;span class="nb"&gt;exec &lt;/span&gt;container2 ping &lt;span class="nt"&gt;-c&lt;/span&gt; 2 &amp;lt;container1-ip-address&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;So, here we can see that one container can ping the other container, so they are interconnected.&lt;/p&gt;

&lt;p&gt;If we now again take a look at our default bridge:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ip &lt;span class="nb"&gt;link &lt;/span&gt;show master docker0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;We will be able to see that there are now two connections from two containers in our &lt;code&gt;docker0&lt;/code&gt; bridge.&lt;/p&gt;

&lt;p&gt;Let's clean up the containers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;docker stop container1 container2
&lt;span class="nb"&gt;sudo &lt;/span&gt;docker &lt;span class="nb"&gt;rm &lt;/span&gt;container1 container2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3.2 Custom Bridge Network
&lt;/h3&gt;

&lt;p&gt;Instead of using the default bridge given by Docker. We can create our custom bridge:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;docker network create my-custom-net 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, if we check the interface list of the host, we will see that there is a new entry. If we check inside it, we will find it empty. This is our new custom bridge.&lt;/p&gt;

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

&lt;p&gt;Let's create two new containers under this network connected to this bridge and check the connection between 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;sudo &lt;/span&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; container1 &lt;span class="nt"&gt;--network&lt;/span&gt; my-custom-net alpine &lt;span class="nb"&gt;sleep &lt;/span&gt;3600
&lt;span class="nb"&gt;sudo &lt;/span&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; container2 &lt;span class="nt"&gt;--network&lt;/span&gt; my-custom-net alpine &lt;span class="nb"&gt;sleep &lt;/span&gt;3600

&lt;span class="nb"&gt;sudo &lt;/span&gt;docker &lt;span class="nb"&gt;exec &lt;/span&gt;container1 ping &lt;span class="nt"&gt;-c&lt;/span&gt; 2 container2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Wait! With default bridge, we needed to get the IP address of container1 to check it from container2. What is going on with custom bridge? We are pinging container2 directly by name. Will this work with default bridge??&lt;/p&gt;

&lt;p&gt;The answer is &lt;strong&gt;No&lt;/strong&gt;. The custom bridge has some advantages over the default bridge. That's why Docker in its docs prefers using user-defined bridges instead of the default bridge. Here are some of the major advantages custom bridge gives over default bridge:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User-defined name for ease of management (e.g., &lt;code&gt;my-network&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Automatic DNS resolution by container name. So we can use the container name instead of its IP.&lt;/li&gt;
&lt;li&gt;Better isolation by grouping containers into different network rather than in a single default network.&lt;/li&gt;
&lt;li&gt;Full control over IP range, gateway, etc.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;** For most of the use case these 3 types of drivers are sufficient **&lt;br&gt;
Let's clean up the containers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;docker stop container1 container2
&lt;span class="nb"&gt;sudo &lt;/span&gt;docker &lt;span class="nb"&gt;rm &lt;/span&gt;container1 container2
&lt;span class="nb"&gt;sudo &lt;/span&gt;docker network &lt;span class="nb"&gt;rm &lt;/span&gt;my-custom-net
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  IPvlan Network Driver:
&lt;/h2&gt;

&lt;p&gt;IPvlan creates virtual interfaces that share the host's MAC address but have different IP addresses. Perfect for scenarios where you need multiple IPs on the same interface. The containers attached directly to host.&lt;/p&gt;

&lt;p&gt;It offers two flavors:&lt;/p&gt;

&lt;p&gt;• L2 mode – host interface behaves like a switch (Layer 2)&lt;br&gt;
• L3 mode – host interface behaves like a router (Layer 3)&lt;/p&gt;

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

&lt;p&gt;The switch-vs-router debate is a rabbit hole for another day🐰. The key point is you pick the mode that matches your network design.&lt;/p&gt;

&lt;p&gt;Let's see how it interacts with the system by trying the L2 mode.&lt;br&gt;
First, we need to know in which gateway the host is on&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ip route
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;So, to create an L2 IPvlan in this network:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;docker network create &lt;span class="nt"&gt;-d&lt;/span&gt; ipvlan &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--subnet&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.110.0/24 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--gateway&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.110.1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;ipvlan_mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;l2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;eth0 &lt;span class="se"&gt;\&lt;/span&gt;
  ipvlan-l2-net
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, if we create a container in this network and check its network interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--network&lt;/span&gt; ipvlan-l2-net alpine sh
ip addr show eth0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;We can see that the MAC address of the interface is the same as the host's, but the IP addresses are different.&lt;/p&gt;

&lt;p&gt;Let's clean up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;docker network &lt;span class="nb"&gt;rm &lt;/span&gt;ipvlan-l2-net
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;WHY &lt;code&gt;IPvlan&lt;/code&gt;:&lt;/strong&gt; Use IPvlan when you need bare-metal network performance (no bridge overhead) or when you must place&lt;br&gt;
containers directly on the same Layer-2 segment as the host. This can bypass the need of NAT/Port mapping to connect to outside network all together.&lt;/p&gt;
&lt;h2&gt;
  
  
  MACvlan Network Driver:
&lt;/h2&gt;

&lt;p&gt;Macvlan creates sub-interfaces with unique MAC addresses which makes containers appear as physical devices on your network. So it can talk to everything else on the network.&lt;/p&gt;

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

&lt;p&gt;MACvlan offers two modes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Bridge mode – every container shares the same flat Layer-2 network; all MAC addresses live in one&lt;br&gt;
broadcast domain.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;VLAN mode – traffic is tagged with 802.1Q VLAN IDs, slicing the broadcast domain into isolated&lt;br&gt;
VLANs while still using the same physical interface.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's try out MacVlan bridge mode. Get the subnet &amp;amp; gateway similar to explained in IPvlan and create a macvlan network:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;docker network create &lt;span class="nt"&gt;-d&lt;/span&gt; macvlan &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--subnet&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.110.0/24 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--gateway&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.110.1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;eth0 &lt;span class="se"&gt;\&lt;/span&gt;
  macvlan-bridge-net
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run two container in this network using macvlan-bridge-net:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--network&lt;/span&gt; macvlan-bridge-net &lt;span class="nt"&gt;--name&lt;/span&gt; container1 &lt;span class="nt"&gt;--ip&lt;/span&gt; 192.168.110.201 alpine &lt;span class="nb"&gt;sleep &lt;/span&gt;3600
&lt;span class="nb"&gt;sudo &lt;/span&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--network&lt;/span&gt; macvlan-bridge-net &lt;span class="nt"&gt;--name&lt;/span&gt; container2 &lt;span class="nt"&gt;--ip&lt;/span&gt; 192.168.110.202 alpine &lt;span class="nb"&gt;sleep &lt;/span&gt;3600
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now ping from any device other than the host. &lt;strong&gt;The host won't be able to ping these IP&lt;/strong&gt; as the host karnel is bypassing its own stack for these addresses.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ping &lt;span class="nt"&gt;-c&lt;/span&gt; 2 192.168.110.201
ping &lt;span class="nt"&gt;-c&lt;/span&gt; 2 192.168.110.201
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When I try to ping the container from a physical device in my network, it results in success. So these containers are now acting like a device on my network with their own IP and MAC addresses.&lt;/p&gt;

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

&lt;p&gt;Let's clean up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;docker stop container1 container2
&lt;span class="nb"&gt;sudo &lt;/span&gt;docker network &lt;span class="nb"&gt;rm &lt;/span&gt;macvlan-bridge-net
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;WHY &lt;code&gt;MACvlan&lt;/code&gt;:&lt;/strong&gt; When you want containers to act like separate physical machines on the same LAN—each with its own MAC and IP—so they’re reachable directly by any host without NAT or port-mapping. The container will act like a standalone server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overlay Network Driver:
&lt;/h2&gt;

&lt;p&gt;Till now, all the networking has been concerned with a single host. What if we have multiple hosts? Now the game is moving towards networking in Kubernetes.&lt;br&gt;
But Docker has a solution for the multi-host problem, its Overlay Network. This is implemented using Docker &lt;code&gt;swarm&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;If we look at the diagram, we can see there is an extra VXLAN connection connecting the host. Now, what is Vxlan?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vxlan:&lt;/strong&gt; VXLAN is an overlay protocol that wraps Layer-2 Ethernet frames inside Layer-3 UDP packets, letting you stitch separate Layer-3 networks into one big, flat Layer-2 domain—so containers on different hosts think they’re plugged into the same switch.&lt;/p&gt;

&lt;p&gt;Now, to play with the overlay network, we need at least 2 hosts. If you are using VMs, just clone it and create another one, and let's go along. If you have only one host, have faith in my screenshots😇.&lt;/p&gt;

&lt;p&gt;Create an overlay network on one host change &lt;code&gt;&amp;lt;manager-ip&amp;gt;&lt;/code&gt; to its ip, which will be the manager:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;docker swarm init &lt;span class="nt"&gt;--advertise-addr&lt;/span&gt; &amp;lt;manager-ip&amp;gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;docker network create &lt;span class="nt"&gt;-d&lt;/span&gt; overlay &lt;span class="nt"&gt;--attachable&lt;/span&gt; demo-overlay
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running the advertise command will output a join command. Now, run that join command on other nodes to join them to this overlay network as worker.&lt;/p&gt;

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

&lt;p&gt;Now we have a overlay network where 2 hosts are connected, create one container on the manager host:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; container1 &lt;span class="nt"&gt;--network&lt;/span&gt; demo-overlay alpine &lt;span class="nb"&gt;sleep &lt;/span&gt;3600
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Crete another container on the worker node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; container2 &lt;span class="nt"&gt;--network&lt;/span&gt; demo-overlay alpine &lt;span class="nb"&gt;sleep &lt;/span&gt;3600
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now run this ping test from the manager:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;docker &lt;span class="nb"&gt;exec &lt;/span&gt;container1 ping &lt;span class="nt"&gt;-c&lt;/span&gt; 2 container2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Success! Think of it, these two containers are in totally different VMs, but they are connected to each other. This is an &lt;code&gt;Overlay&lt;/code&gt; network.&lt;/p&gt;

&lt;p&gt;Let's clean up:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;On the worker:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;docker stop container2
&lt;span class="nb"&gt;sudo &lt;/span&gt;docker swarm leave &lt;span class="nt"&gt;--force&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;On the manager:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;docker stop container1
&lt;span class="nb"&gt;sudo &lt;/span&gt;docker network &lt;span class="nb"&gt;rm &lt;/span&gt;demo-overlay
&lt;span class="nb"&gt;sudo &lt;/span&gt;docker swarm leave &lt;span class="nt"&gt;--force&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;WHY &lt;code&gt;Overlay&lt;/code&gt;:&lt;/strong&gt; Overlay network comes in when you want to go with multi-host deployment with something like Docker Swarm service and want your containers to communicate across hosts physical or virtual boundaries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Mission accomplished😰 so it’s done! We’ve covered all six networking drivers Docker offers, giving you the flexibility to tailor container connectivity to nearly any use case. From fully isolated sandboxes to multi-host overlays. I hope you’ve tried out the examples and seen for yourself how Linux networking makes all of these configurations possible. Another key piece of the puzzle is cgroups. Stay tuned we’ll dive into that concept another day. Till then, happy dockering!🐳&lt;/p&gt;

</description>
      <category>docker</category>
      <category>networking</category>
      <category>programming</category>
      <category>devops</category>
    </item>
    <item>
      <title>Breaking Free from AI Subscriptions: Cost-Effective All-in-One Solution with OpenRouter</title>
      <dc:creator>PH Saurav</dc:creator>
      <pubDate>Sat, 15 Mar 2025 19:57:25 +0000</pubDate>
      <link>https://dev.to/phsaurav/breaking-free-from-ai-subscriptions-cost-effective-all-in-one-solution-with-openrouter-3ce</link>
      <guid>https://dev.to/phsaurav/breaking-free-from-ai-subscriptions-cost-effective-all-in-one-solution-with-openrouter-3ce</guid>
      <description>&lt;p&gt;AI is here to stay. From my early days with the Copilot trial to now I am using it in different flavours and capacities. I've become convinced that AI properly used especially, in Software Engineering can significantly boost development productivity and accelerate learning. By the end of this article, I'll share my approach to leveraging AI. If you're still hesitant to use AI out of fear it will take your job, consider this: there's a much higher probability that colleagues who become proficient with AI tools will ultimately replace you instead. To me, this resistance is similar to avoiding Google searches in favour of manually reading documentation—it sounds admirable in theory but is ultimately less practical and runs counter to how modern work happens.&lt;/p&gt;

&lt;p&gt;So having said that, one issue with every service nowadays is they're all subscription-based, and moreover, it's often not enough to have just one subscription. Some content is only available on Netflix, and other things are exclusively on Hulu. I felt similarly about major AI models as well—Copilot Pro, Claude Pro, and Grok Pro all require monthly subscriptions. We have Copilot, Tabnine, Cody, and many others in the coding space. &lt;/p&gt;

&lt;p&gt;The recent release of models is becoming overwhelming, with Grok, R1, Gemini, and others rapidly emerging to challenge the monopoly of OpenAI and Anthropic. For someone like me who uses these models infrequently or in bursts and loves to play with different models to get a better grasp of improvements in AI, paying for subscriptions feels like a waste of money. Of course, you can purchase API keys and pay for credits as you go, but then you're managing different billing accounts and keys for each model, which is hassle on another level. If only there were a single platform that could solve this problem that going to allow you to use any model you want while simply paying for actual usage. I started searching for a solution for this goal and I've found a solution and I will share all about it in this article.&lt;/p&gt;

&lt;h2&gt;
  
  
  Goal to achieve
&lt;/h2&gt;

&lt;p&gt;Before explaining the solution, let me provide a brief overview of how I use AI in my workflow. This context will help you understand why I chose this particular approach.&lt;br&gt;
I primarily use AI for interactive chat functionality. I don't prefer automatic code generation or AI autocomplete features, though you can achieve cursor-like code generation with this approach if desired. This wasn't my primary goal because I believe generating large sections of code or multiple files simultaneously via AI isn't ideal. Such practices can introduce problematic code that degrades quality and maintainability.&lt;br&gt;
Instead, I prefer using AI as a collaborative companion—a tool to discuss issues with, solve specific problems, and suggest improvements. For this workflow, two essential features were critical:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Provide better pricing for variable usage patterns (Pay on Usage)&lt;/li&gt;
&lt;li&gt;The ability to easily add coding context to chat conversations&lt;/li&gt;
&lt;li&gt;A simple way to experiment with and switch between different AI models&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  OpenRouter a unified interface (All LLM in One API)
&lt;/h2&gt;

&lt;p&gt;In my search for a comprehensive AI solution, I explored various approaches and discovered OpenRouter—a unified API gateway that provides access to about 300 models from major providers including every model from OpenAI, Anthropic, Google, Deepseek, and countless others. If a model is publicly available it will be available in OpenRouter. It serves as an excellent platform to monitor recent AI trends and compare model performance side-by-side.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fspe54wmb0tu4r5g5idhj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fspe54wmb0tu4r5g5idhj.png" alt="Openrouter Chat Room Interface" width="800" height="578"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;OpenRouter even offers a multi-model chat interface where you can test and experiment with different models simultaneously. Each model is accessible through a straightforward API interface using a single API key—which is precisely what we'll be utilizing in our next steps.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftyvzgsplkuiospg6630f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftyvzgsplkuiospg6630f.png" alt="Token Usage and Cost Report Per Prompt for last 30 days" width="800" height="597"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Perhaps the most appealing aspect is OpenRouter's transparent pricing structure. Costs are clearly displayed for each model and prompt, with no additional fees beyond what you'd pay through official APIs. Integration is remarkably straightforward, as most custom AI chat extensions in IDEs already support the OpenRouter API protocol.&lt;/p&gt;

&lt;h2&gt;
  
  
  CopilotChat Replacement in IDE
&lt;/h2&gt;

&lt;p&gt;If you just want to chat with different models then OpenRouter's chat interface is quite sufficient but for us software engineers we need to integrate it into the IDE to supercharge the power with coding context and a easy interface. As I mainly use VSCode, JetBrains IDE &amp;amp; NeoVim. I will share my tooling and configuration with some additional options available for different workflows. &lt;/p&gt;

&lt;h3&gt;
  
  
  For Visual Studio Code &amp;amp; Jetbrain IDE's
&lt;/h3&gt;

&lt;p&gt;For these GUI-based IDEs, my preferred choice is the Continue extension. It's exceptionally straightforward, easily configurable, and comes with clear &lt;a href="https://docs.continue.dev/chat/how-to-use-it" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;. Alternatives like Cline provide a more Cursor-like agentic code generation experience which isn't my preference for my workflow, but may appeal to others looking for that functionality. If anyone into that they can use Cline which is more popular VSCode Plugin. &lt;/p&gt;

&lt;p&gt;Setup is very simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install the Extension.&lt;/li&gt;
&lt;li&gt;Setup the Continue Local Config with model data
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"models"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Flash 2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"provider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"openrouter"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"google/gemini-2.0-flash-001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"apiBase"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://openrouter.ai/api/v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"apiKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OPENROUTER-API-KEY"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Claude 3.7 Sonnet"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"provider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"openrouter"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"anthropic/claude-3.7-sonnet"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"apiBase"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://openrouter.ai/api/v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"apiKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OPENROUTER-API-KEY"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DeepSeek: R1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"provider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"openrouter"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"deepseek/deepseek-r1:free"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"apiBase"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://openrouter.ai/api/v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"apiKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OPENROUTER-API-KEY"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OpenAI: o3 Mini"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"provider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"openrouter"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"openai/o3-mini"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"apiBase"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://openrouter.ai/api/v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"apiKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OPENROUTER-API-KEY"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"contextProviders"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"slashCommands"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"share"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Export the current chat session to markdown"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cmd"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Generate a shell command"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"commit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Generate a git commit message"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this configuration, I've added four models from four different LLM providers: Google's Gemini 2.0 Flash, Antropics Claude-3.7-sonnet, Deepseek's R1 and OpenAi's o3 Mini. You can add any model from the OpenRouter API you want. Now, you can choose any of these models from the chat Interface and add context with the @ symbol.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft7pcvaejs86mv1myrvs8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft7pcvaejs86mv1myrvs8.png" alt="Visual Studio Continue with Openrouter Local Config&amp;lt;br&amp;gt;
" width="800" height="770"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  For Neovim
&lt;/h3&gt;

&lt;p&gt;I tried many options for Neovim, and everyone is quite hyped about Avante. But to me, it felt like a lot of tooling I don't need as it is more focused for cursor-like code generation agentic workflow. So if anyone into that they will I think really like avante. &lt;br&gt;
After testing many options, I ultimately selected &lt;a href="https://codecompanion.olimorris.dev/" rel="noopener noreferrer"&gt;CodeCompanion&lt;/a&gt;. It impressed me with its simplicity while still being feature-rich. It provides excellent support for different context providers, makes switching between models effortless, and maintains the lightweight experience I value in my Neovim setup.&lt;br&gt;
&lt;a href="https://codecompanion.olimorris.dev/" rel="noopener noreferrer"&gt;CodeCompanion&lt;/a&gt; also offers clear, comprehensive documentation, making it straightforward to implement advanced features if anyone wants it.&lt;/p&gt;

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

&lt;p&gt;To replicate similar experiences in Neovim you can check my config from my dotfile repo:&lt;br&gt;
&lt;a href="https://github.com/phsaurav/dotfiles/blob/main/.config/nvim/lua/ph/plugins/code-companion.lua" rel="noopener noreferrer"&gt;.dotfile: code-companion.lua&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bonus tip: If you use Lualine, you can integrate a loading animation &amp;amp; current model selection for CodeCompanion that significantly enhances your user experience while processing requests.&lt;br&gt;
&lt;a href="https://github.com/phsaurav/dotfiles/blob/bd271cfbbe262e9f830663133d84f131a3357140/.config/nvim/lua/ph/plugins/lualine.lua" rel="noopener noreferrer"&gt;.dotfile: lualine.lua&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/phsaurav/dotfiles/blob/bd271cfbbe262e9f830663133d84f131a3357140/.config/nvim/lua/ph/utils/spinner.lua" rel="noopener noreferrer"&gt;.dotfile: spinner.lua&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can follow the getting started guide and set the OPENROUTER_API_KEY in the environment. It's good to go. The experience is in my opinion much better and polished than GitHub Copilot Chat in Neovim.&lt;/p&gt;

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

&lt;p&gt;After using this setup, I don't miss Copilot &amp;amp; Tabnine Chat at all. In fact, I'm enjoying the additional features this approach provides. Regarding cost, if someone uses expensive models like Claude 3.7 heavily - especially with agentic workflows or you are into "Vibe Coding" where millions of lines of context are shared - then a subscription service like Copilot might be more cost-efficient.&lt;/p&gt;

&lt;p&gt;However, for occasional users like me, this approach is very affordable. Plus, you're not dependent on a provider's goodwill to access the latest models. When an API becomes publicly available, you can immediately integrate it into your workflow. In my opinion, this flexibility is invaluable in today's rapidly evolving AI landscape.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>openrouter</category>
      <category>programming</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Why DB Migration Versioning when we have GIT?</title>
      <dc:creator>PH Saurav</dc:creator>
      <pubDate>Mon, 25 Nov 2024 09:31:29 +0000</pubDate>
      <link>https://dev.to/phsaurav/why-db-migration-versioning-when-we-have-git-31ai</link>
      <guid>https://dev.to/phsaurav/why-db-migration-versioning-when-we-have-git-31ai</guid>
      <description>&lt;p&gt;Database migration versioning is often a notorious pain point in software development. Anyone who worked in a big enough team merging PRs knows it is quite gross to manage and a frequent source of conflict.&lt;/p&gt;

&lt;p&gt;Naturally, this begs the question: if we already have robust version control systems like Git, why do we need an additional layer of version control for database migrations within a version control?&lt;/p&gt;

&lt;p&gt;I once used to be one of those developers questioning this necessity and researched the topic deeply. Recently, after coming across a recent video by a popular developer and YouTuber Theo titled &lt;a href="https://youtu.be/jeWrbAiA1D0?si=wf0V3OFkaYbWzJWq" rel="noopener noreferrer"&gt;"We Don't Need Migrations Anymore"&lt;/a&gt;. I thought of sharing my understanding and taking on this in a simple manner with an example.&lt;/p&gt;

&lt;p&gt;Let’s dive into why DB Migration versioning may be gross but may be less gross than anything else we have.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background Information
&lt;/h2&gt;

&lt;p&gt;When working on a project alone, managing the database schema during development usually doesn’t require much thought. However, once the first release is made, every schema change must be applied not only to the local instance but also to other environments like development, staging, and production. At this point, it becomes essential to establish a system to orchestrate synchronization across the team and between environments.&lt;/p&gt;

&lt;p&gt;Then, we have two common approaches we can take to orchestrate DB schema change:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Declarative Schema Management&lt;/strong&gt;&lt;br&gt;
In this approach, a central database schema represents the desired state. Any changes to the desired schema are compared with the current state, and the differences are used to generate the appropriate SQL queries to transition the database from its current state to the desired state. This is very similar to how Terraform works. This method has been gaining significant popularity recently, especially among infrastructure engineers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Imperative Schema Management (Migrations)&lt;/strong&gt;&lt;br&gt;
In this approach, there is no central database schema. Instead, changes are applied iteratively and represented as a series of instruction sets. Each set of SQL instructions is referred to as a "Migration." Migrations track both the previous and the next revision or version, forming a sequential chain of instructions that are executed in a specific "ORDER."&lt;/p&gt;

&lt;p&gt;Now, as the basics are out of the way, let's see why one should choose one or the other. &lt;/p&gt;

&lt;p&gt;At first glance, the declarative approach seems much simpler. You only need to maintain a central schema, and the rest is handled automatically. In contrast, migrations can quickly become messy. For instance, imagine two team members working on separate branches, both originating from a branch where the latest migration is A. Each developer creates a new migration, B and C, respectively. When it’s time to merge the changes, conflicts naturally arise: which migration should come after A —&amp;gt; B or C?&lt;/p&gt;

&lt;p&gt;So why not use the declarative approach in every scenario? Let's understand this from an example.&lt;/p&gt;
&lt;h2&gt;
  
  
  Example Scenario: E-commerce Platform Expansion
&lt;/h2&gt;

&lt;p&gt;Suppose your company runs an e-commerce platform that serves thousands of customers daily. The backend is powered by a relational database for managing products, orders, users, and payments deployed in production and users are already using it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Initial Database Schema&lt;/strong&gt;&lt;br&gt;
The products table in the database has the following structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;stock&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Requirement Change&lt;/strong&gt; &lt;br&gt;
To support global expansion, the business has decided to introduce currency support for products and rename the &lt;code&gt;name&lt;/code&gt; column to &lt;code&gt;user_name&lt;/code&gt;. The changes are as follows:  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add a &lt;code&gt;currency&lt;/code&gt; column to the &lt;code&gt;products&lt;/code&gt; table, ensuring it is not nullable. Currency value should be provided on product creation. Old products should be considered as 'USD'.
&lt;/li&gt;
&lt;li&gt;Rename the &lt;code&gt;name&lt;/code&gt; column to &lt;code&gt;user_name&lt;/code&gt;, preserving all existing data.
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Declarative Approach
&lt;/h2&gt;

&lt;p&gt;As in the declarative approach, we maintain the desired schema, so the updated schema will be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;user_name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;stock&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
    &lt;span class="n"&gt;currency&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great! Now, we can call it a day. Not so easy!😁&lt;/p&gt;

&lt;h2&gt;
  
  
  Issues:
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Existing Data Incompatibility&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        ERROR: column "stock" contains null values.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adding a &lt;code&gt;NOT NULL&lt;/code&gt; column (&lt;code&gt;currency&lt;/code&gt; in &lt;code&gt;products&lt;/code&gt;) directly without default values breaks the database if rows already exist.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Less control on specific instruction:&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To change &lt;code&gt;name&lt;/code&gt; to &lt;code&gt;user_name&lt;/code&gt; and achieve the desired state, there are two possible approaches:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Drop the &lt;code&gt;name&lt;/code&gt; column and create a new &lt;code&gt;user_name&lt;/code&gt; column.
&lt;/li&gt;
&lt;li&gt;Alter the &lt;code&gt;name&lt;/code&gt; column to &lt;code&gt;user_name&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first approach can be catastrophic for your application, and you don't have specific controls on which one will be implemented in the declarative approach.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Running in wrong "ORDER":&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One important thing about SQL instructions is that they need to run in order. Suppose that, in the generated instruction, the 2nd step runs before the first one. It will cause an error. The declarative approach doesn't give us control over the execution order.&lt;/p&gt;

&lt;h2&gt;
  
  
  Possible Solution:
&lt;/h2&gt;

&lt;p&gt;These steps can be taken to meet the new requirement for our database.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Add the currency column&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;
    &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;currency&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'USD'&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Rename the 'name' column to 'user_name'&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;
    &lt;span class="k"&gt;RENAME&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;user_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2 (Optional):
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Ensure all existing products have default values&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;currency&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'USD'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Remove default constraints to make columns explicitly set by API&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;currency&lt;/span&gt; &lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why Migrations?
&lt;/h2&gt;

&lt;p&gt;Now, one can argue we can do these three steps in order one after another in a declarative approach and achieve the desired schema state without any issues. It's true. Then the approach will look something like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add the &lt;code&gt;currency&lt;/code&gt; column with default and add the &lt;code&gt;user_name&lt;/code&gt; column in the product table.&lt;/li&gt;
&lt;li&gt;Deploy the changes.&lt;/li&gt;
&lt;li&gt;Ensure the &lt;code&gt;currency&lt;/code&gt; column existing data is filled and copy the &lt;code&gt;name&lt;/code&gt; data to &lt;code&gt;user_name&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Run instructions in deployed DB.&lt;/li&gt;
&lt;li&gt;Drop the &lt;code&gt;name&lt;/code&gt; column and drop the default for the &lt;code&gt;currency&lt;/code&gt; column.&lt;/li&gt;
&lt;li&gt;Deploy the changes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This may seem something very much doable when you are working with a local DB. But this will introduce a huge headache if you are doing it in a production environment where, most of the time, you don't have access to DB directly and orchestrating these changes to multiple environments can become a nightmare very quickly. Also, multiple people working on this kind of change can lead to possible catastrophes. But this can be a very good approach if the schema is controlled centrally by an infrastructure engineer rather than a developer. I think this is why the declarative approach is increasingly gaining popularity among infrastructure engineers.&lt;/p&gt;

&lt;p&gt;Now, migrations solve these issues and make them safe by giving developers the ability to choose "Specific Approach" and "ORDER". Even one can run each migration with an SQL transaction, so if one fails, all the other instructions in the transaction will roll back, making it safer. It is easier to review the migrations and also easier to orchestrate specific implementation in different environments.&lt;/p&gt;

</description>
      <category>database</category>
      <category>softwareengineering</category>
      <category>backend</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Managing Multiple Shadow Cloud Architectures Across Development, Staging, and Production Environments with Terraform Workspace</title>
      <dc:creator>PH Saurav</dc:creator>
      <pubDate>Mon, 28 Oct 2024 14:41:07 +0000</pubDate>
      <link>https://dev.to/phsaurav/separate-aws-accounts-for-dev-staging-and-production-in-a-terraform-multi-workspace-environment-3k6h</link>
      <guid>https://dev.to/phsaurav/separate-aws-accounts-for-dev-staging-and-production-in-a-terraform-multi-workspace-environment-3k6h</guid>
      <description>&lt;p&gt;Managing multiple shadow cloud architectures across development, staging, and production environments—where architectures are similar but not identical and deployed in different AWS accounts can be complex and challenging. There are various approaches for handling this depending on specific scenarios and requirements. In a recent project, I faced this exact scenario but found limited resources to guide me through. So, I decided to document my approach and share the solution I developed in this blog. So, others in similar situations can use this as a guide. I hope my research and experimentation save you some time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Goals and Scenario
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Set up Development, Staging, and Production environments with similar architectures but differing resource types and sizes.&lt;/li&gt;
&lt;li&gt;Consolidate these environments into a single Terraform project to reduce complexity and avoid redundant configurations.&lt;/li&gt;
&lt;li&gt;Deploy each environment in a separate AWS account.&lt;/li&gt;
&lt;li&gt;Implement remote state management and enable state locking for consistency and collaboration.&lt;/li&gt;
&lt;li&gt;Establish safeguards to prevent critical mistakes and ensure safe deployments.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Plan for Implementation
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Use Terraform workspaces and a single remote state source to manage state across environments.&lt;/li&gt;
&lt;li&gt;Utilize separate variable files to handle environment-specific differences.&lt;/li&gt;
&lt;li&gt;Configure AWS profiles at runtime to manage deployments across different AWS accounts.&lt;/li&gt;
&lt;li&gt;Implement an environment validator to prevent critical errors.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Setup Process
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Setup AWS Credentials:
&lt;/h3&gt;

&lt;p&gt;First, we need to set AWS credentials for our accounts. For detailed instructions, follow this doc &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html" rel="noopener noreferrer"&gt;Configuration and credential file settings in the AWS CLI&lt;/a&gt;. In the AWS credentials file (~/.aws/credentials), add credentials with names similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[app-dev]
aws_access_key_id = &amp;lt;AWS ACCESS KEY&amp;gt;
aws_secret_access_key = &amp;lt;AWS SECRET KEY&amp;gt;

[app-stage]
aws_access_key_id = &amp;lt;AWS ACCESS KEY&amp;gt;
aws_secret_access_key = &amp;lt;AWS SECRET KEY&amp;gt;

[app-prod]
aws_access_key_id = &amp;lt;AWS ACCESS KEY&amp;gt;
aws_secret_access_key = &amp;lt;AWS SECRET KEY&amp;gt;
aws_session_token= &amp;lt;AWS SESSION KEY&amp;gt;   # IF Needed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, the names of the profiles &lt;code&gt;app-dev,&lt;/code&gt; &lt;code&gt;app-stage,&lt;/code&gt; and &lt;code&gt;app-prod&lt;/code&gt; are important. With these, we will dynamically choose an AWS account to deploy.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Setup Remote Backend for State Management &amp;amp; Locking
&lt;/h3&gt;

&lt;p&gt;In this approach, we’ll use a single backend to manage the architecture states, leveraging Terraform’s workspace feature to separate the state for each environment. We’ll store the states in an S3 bucket and use DynamoDB for state locking. To set this up, create an S3 bucket and a DynamoDB table with a partition key named &lt;code&gt;LockID&lt;/code&gt; in your account for state storage. For a detailed guide, check out this &lt;a href="https://medium.com/@aaloktrivedi/configuring-a-terraform-remote-backend-with-s3-and-dynamodb-ebcefa8432ea" rel="noopener noreferrer"&gt;article&lt;/a&gt;. In our setup, we’re using the production account to store states, so we created the S3 bucket and DynamoDB table there. Next, let’s configure the backend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;backend&lt;/span&gt; &lt;span class="s2"&gt;"s3"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;bucket&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"bucket-name"&lt;/span&gt;
    &lt;span class="nx"&gt;encrypt&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="nx"&gt;key&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"app-name/terraform.tfstate"&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;"region"&lt;/span&gt;
    &lt;span class="nx"&gt;dynamodb_table&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"dynamodb-table-name"&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 initialize the environment. Before doing that, we need to set the account profile in which we have created the backend as our default profile for this environment. We've set this in the &lt;code&gt;app-prod&lt;/code&gt; account, so we will set this as default. It is important to set this. Otherwise, the initialization will fail. Also, you need to run this before a session. Otherwise, you can also set this as your default profile. This way, you don't need to export it in your environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AWS_PROFILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;app-prod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now run terraform init:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform init  &lt;span class="c"&gt;#-reconfigure tag to avoid conflict not always necessary&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Setup Terraform Workspaces
&lt;/h3&gt;

&lt;p&gt;Create workspaces for dev, stage, and prod environments with the workspace &lt;code&gt;new&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform workspace new &amp;lt;workspace-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To switch to a different workspace, use the &lt;code&gt;select&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform workspace &lt;span class="k"&gt;select &lt;/span&gt;dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Base Project Setup
&lt;/h3&gt;

&lt;p&gt;Now, in the main project setup, the AWS provider takes a profile dynamically in runtime from the variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 1.9.0"&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; 5.72.1"&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;# * provider block&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;profile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;   &lt;span class="c1"&gt;#Dynamically in runtime&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_region&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;variables.tf&lt;/code&gt;, we will set the required variables:&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;# * General &lt;/span&gt;
&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"aws_region"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AWS Region to use"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"profile"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AWS profile to use"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Setup &lt;code&gt;.tfvars&lt;/code&gt; for Each Environment:
&lt;/h3&gt;

&lt;p&gt;We’ll manage environment-specific differences using variable files—special files that store key-value pairs for each configuration. By supplying these files at runtime, we can easily apply unique configurations for each environment. Below is an example for dev and prod; any additional environment would follow a similar structure.&lt;br&gt;
Example of &lt;code&gt;dev.tfvars&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="c1"&gt;# Generic variables&lt;/span&gt;
&lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"dev"&lt;/span&gt;    &lt;span class="c1"&gt;#Environment name for validation&lt;/span&gt;
&lt;span class="nx"&gt;project&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"app-name"&lt;/span&gt;
&lt;span class="nx"&gt;aws_region&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
&lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"app-dev"&lt;/span&gt;    &lt;span class="c1"&gt;#AWS profile name for this environment&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Example of &lt;code&gt;prod.tfvars&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="c1"&gt;# Generic variables&lt;/span&gt;
&lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"prod"&lt;/span&gt;    &lt;span class="c1"&gt;#Environment name for validation&lt;/span&gt;
&lt;span class="nx"&gt;project&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"app-name"&lt;/span&gt;
&lt;span class="nx"&gt;aws_region&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
&lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"app-prod"&lt;/span&gt;    &lt;span class="c1"&gt;#AWS profile name for this environment&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, to use a specific &lt;code&gt;.tfvars&lt;/code&gt; variable file for an environment, we can use the &lt;code&gt;-var-file&lt;/code&gt; flag.&lt;/p&gt;

&lt;p&gt;For example, for a dev environment, we can use this command to create a plan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform workspace &lt;span class="k"&gt;select &lt;/span&gt;dev &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; terraform plan &lt;span class="nt"&gt;-var-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"dev.tfvars"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similarly, to apply changes to the prod environment, we can use the command like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform workspace &lt;span class="k"&gt;select &lt;/span&gt;prod &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; terraform apply &lt;span class="nt"&gt;-var-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"prod.tfvars"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Safeguard to Prevent Catastrophe
&lt;/h3&gt;

&lt;p&gt;Looking at the current structure, there’s a significant risk: a developer could accidentally apply the variable file for one environment to another, which could be disastrous—especially for production. To mitigate this, we can implement a validator that verifies the match between the variable file and the current workspace, ensuring the correct &lt;code&gt;tfvars&lt;/code&gt; file is used for each workspace.&lt;br&gt;
In our &lt;code&gt;variables.tf&lt;/code&gt; we will add this variable and validator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"environment"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Environment Name"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;

  &lt;span class="nx"&gt;validation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;==&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;workspace&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Workspace &amp;amp; Variable File Inconsistency!! Please Double-check!"&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;In our &lt;code&gt;.tfvars&lt;/code&gt; file, we already have a variable called &lt;code&gt;environment&lt;/code&gt; that will hold the name of the environment it is for. &lt;/p&gt;

&lt;p&gt;Now, if the current environment and the environment of the &lt;code&gt;.tfvars&lt;/code&gt; file don't match, it will generate an error similar to this:&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Conclusion and some tips
&lt;/h3&gt;

&lt;p&gt;Now, this is it, and the environment is set. This is a great approach if the architecture for your project is quite similar for all environments. Otherwise, separate projects for all environments with module resources may be better suited. &lt;br&gt;
Some common errors you may face:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If you see an &lt;code&gt;Error: No valid credential sources found&lt;/code&gt; error at initiation, most probably you don't have any default profile set, so run the export environment command again, as mentioned in Step 2.&lt;/li&gt;
&lt;li&gt;If your credential has a session key, it will expire after a period, and in that case, you have to update your AWS account credentials.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>terraform</category>
      <category>aws</category>
      <category>cloud</category>
      <category>backend</category>
    </item>
  </channel>
</rss>
