# Building a High Availability Cluster with HAProxy, Keepalived, and Docker: A Step-by-Step Guide

Link: [https://medium.com/@yahyasghiouri1998/building-a-high-availability-cluster-with-haproxy-keepalived-and-docker-a-step-by-step-guide-9325f4ac8aa7](https://medium.com/@yahyasghiouri1998/building-a-high-availability-cluster-with-haproxy-keepalived-and-docker-a-step-by-step-guide-9325f4ac8aa7)

<div class="ac cb" id="bkmrk-yahya-sghiouri-follo" style="text-align: justify;"><div class="ci bh gb gc gd ge"><div><div><div class="speechify-ignore ac cp"><div class="speechify-ignore bh m"><div class="ac hx hy hz ia ib ic id ie if ig ih"><div class="ac r ih"><div class="ac ii"><div><div aria-describedby="1" aria-hidden="false" aria-labelledby="1" class="bm"><div class="be" tabindex="-1"></div></div></div></div></div><div class="ac r iz"><div class="ac af"><span aria-hidden="true" class="m"><span class="bf b bg ab du">·</span></span><span data-testid="storyPublishDate">Aug 30, 2024</span></div></div></div><div class="ac cp jc jd je jf jg jh ji jj jk jl jm jn jo jp jq jr"><div class="i l x fi fj r"><div class="kh m"><div class="ac r ki kj"><div class="pw-multi-vote-icon fl kk kl km kn">[<div><div aria-describedby="3" aria-hidden="false" aria-labelledby="3" class="bm"><div class="be" tabindex="-1"><div class="ko ap kp kq kr ks an kt ku kv kn" role="presentation"><svg aria-label="clap" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path clip-rule="evenodd" d="M11.37.828 12 3.282l.63-2.454zM13.916 3.953l1.523-2.112-1.184-.39zM8.589 1.84l1.522 2.112-.337-2.501zM18.523 18.92c-.86.86-1.75 1.246-2.62 1.33a6 6 0 0 0 .407-.372c2.388-2.389 2.86-4.951 1.399-7.623l-.912-1.603-.79-1.672c-.26-.56-.194-.98.203-1.288a.7.7 0 0 1 .546-.132c.283.046.546.231.728.5l2.363 4.157c.976 1.624 1.141 4.237-1.324 6.702m-10.999-.438L3.37 14.328a.828.828 0 0 1 .585-1.408.83.83 0 0 1 .585.242l2.158 2.157a.365.365 0 0 0 .516-.516l-2.157-2.158-1.449-1.449a.826.826 0 0 1 1.167-1.17l3.438 3.44a.363.363 0 0 0 .516 0 .364.364 0 0 0 0-.516L5.293 9.513l-.97-.97a.826.826 0 0 1 0-1.166.84.84 0 0 1 1.167 0l.97.968 3.437 3.436a.36.36 0 0 0 .517 0 .366.366 0 0 0 0-.516L6.977 7.83a.82.82 0 0 1-.241-.584.82.82 0 0 1 .824-.826c.219 0 .43.087.584.242l5.787 5.787a.366.366 0 0 0 .587-.415l-1.117-2.363c-.26-.56-.194-.98.204-1.289a.7.7 0 0 1 .546-.132c.283.046.545.232.727.501l2.193 3.86c1.302 2.38.883 4.59-1.277 6.75-1.156 1.156-2.602 1.627-4.19 1.367-1.418-.236-2.866-1.033-4.079-2.246M10.75 5.971l2.12 2.12c-.41.502-.465 1.17-.128 1.89l.22.465-3.523-3.523a.8.8 0 0 1-.097-.368c0-.22.086-.428.241-.584a.847.847 0 0 1 1.167 0m7.355 1.705c-.31-.461-.746-.758-1.23-.837a1.44 1.44 0 0 0-1.11.275c-.312.24-.505.543-.59.881a1.74 1.74 0 0 0-.906-.465 1.47 1.47 0 0 0-.82.106l-2.182-2.182a1.56 1.56 0 0 0-2.2 0 1.54 1.54 0 0 0-.396.701 1.56 1.56 0 0 0-2.21-.01 1.55 1.55 0 0 0-.416.753c-.624-.624-1.649-.624-2.237-.037a1.557 1.557 0 0 0 0 2.2c-.239.1-.501.238-.715.453a1.56 1.56 0 0 0 0 2.2l.516.515a1.556 1.556 0 0 0-.753 2.615L7.01 19c1.32 1.319 2.909 2.189 4.475 2.449q.482.08.971.08c.85 0 1.653-.198 2.393-.579.231.033.46.054.686.054 1.266 0 2.457-.52 3.505-1.567 2.763-2.763 2.552-5.734 1.439-7.586z" fill-rule="evenodd"></path></svg></div></div></div></div>](https://medium.com/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fvote%2Fp%2F9325f4ac8aa7&operation=register&redirect=https%3A%2F%2Fmedium.com%2F%40yahyasghiouri1998%2Fbuilding-a-high-availability-cluster-with-haproxy-keepalived-and-docker-a-step-by-step-guide-9325f4ac8aa7&user=Yahya+sghiouri&userId=926c8b21d31f&source=---header_actions--9325f4ac8aa7---------------------clap_footer------------------)</div><div class="pw-multi-vote-count m kw kx ky kz la lb lc"><div><div aria-describedby="180" aria-hidden="false" aria-labelledby="180" class="bm"></div></div></div></div></div></div></div></div></div></div></div></div></div><div class="ml" id="bkmrk-high-availability" style="text-align: justify;"><div class="ac cb"><div class="ln mm lo mn lp mo cf mp cg mq ci bh"><figure class="mu mv mw mx my ml mz na paragraph-image"><div class="nb nc fl nd bh ne" role="button" tabindex="0"><div class="mr ms mt"><picture><source sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 1000px" srcset="https://miro.medium.com/v2/resize:fit:640/format:webp/1*qGOZWP0mzHb2II-vrNx7Bg.jpeg 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/1*qGOZWP0mzHb2II-vrNx7Bg.jpeg 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/1*qGOZWP0mzHb2II-vrNx7Bg.jpeg 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/1*qGOZWP0mzHb2II-vrNx7Bg.jpeg 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/1*qGOZWP0mzHb2II-vrNx7Bg.jpeg 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/1*qGOZWP0mzHb2II-vrNx7Bg.jpeg 1100w, https://miro.medium.com/v2/resize:fit:2000/format:webp/1*qGOZWP0mzHb2II-vrNx7Bg.jpeg 2000w" type="image/webp"><source data-testid="og" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 1000px" srcset="https://miro.medium.com/v2/resize:fit:640/1*qGOZWP0mzHb2II-vrNx7Bg.jpeg 640w, https://miro.medium.com/v2/resize:fit:720/1*qGOZWP0mzHb2II-vrNx7Bg.jpeg 720w, https://miro.medium.com/v2/resize:fit:750/1*qGOZWP0mzHb2II-vrNx7Bg.jpeg 750w, https://miro.medium.com/v2/resize:fit:786/1*qGOZWP0mzHb2II-vrNx7Bg.jpeg 786w, https://miro.medium.com/v2/resize:fit:828/1*qGOZWP0mzHb2II-vrNx7Bg.jpeg 828w, https://miro.medium.com/v2/resize:fit:1100/1*qGOZWP0mzHb2II-vrNx7Bg.jpeg 1100w, https://miro.medium.com/v2/resize:fit:2000/1*qGOZWP0mzHb2II-vrNx7Bg.jpeg 2000w">![](https://miro.medium.com/v2/resize:fit:990/1*qGOZWP0mzHb2II-vrNx7Bg.jpeg)</source></source></picture></div></div><figcaption class="ng ff nh mr ms ni nj bf b bg ab du" data-selectable-paragraph="">High availability</figcaption></figure></div></div></div><span class="m oj ok ol bo om on oo op oq fl">H</span>igh availability (HA) is essential for ensuring that web applications remain accessible, even in the face of hardware or software failures. An HA architecture distributes traffic across multiple servers, preventing any single point of failure from disrupting service. In this article, we’ll explore how to build a high availability cluster using HAProxy and Keepalived, two powerful tools for load balancing and failover.

To enhance flexibility and simplify deployment, we’ll leverage Docker to containerize HAProxy, Keepalived, and our web applications. Docker allows us to create a portable and easily manageable HA setup that can be deployed across different environments, whether on-premises or in the cloud.

Throughout this guide, we’ll walk you through the entire process — from setting up Docker networks and building Dockerfiles to configuring HAProxy and Keepalived for seamless failover. By the end, you’ll have a fully functional HA cluster that ensures your web applications are always available, all within a Dockerized environment.

# I-/ General Concepts

Before diving into the different configurations, it’s helpful to understand the core components of the architecture.

## I-1/HAproxy

**HAProxy**, as its name suggests, stands for High Availability Proxy. It is a robust and versatile tool designed to provide high availability and efficient load balancing for network traffic.

HAProxy is a widely used tool for distributing incoming requests across multiple backend servers to enhance both reliability and performance. It performs continuous health checks on these servers to ensure that traffic is routed only to those that are healthy and responsive.

The tool employs various algorithms, such as round-robin and least connections, to effectively balance the load. Supporting both TCP and HTTP traffic, HAProxy operates at Layer 4 (the transport layer) and Layer 7 (the application layer) of the OSI model.

There are two main components when configuring HAproxy , a frontend and a backend section.

<div class="ac cb" id="bkmrk-frontend-section%3A%C2%A0it" style="text-align: justify;"><div class="ci bh gb gc gd ge">- **Frontend section:** It is the entry point for incoming client requests. It defines how HAProxy listens for incoming traffic and how it should handle these requests, here we specify the address and port on which HAProxy should listen, as well as any rules or conditions for routing the traffic to the appropriate backend by inspecting the incoming packets.
- **Backend section:** it represents the servers that will handle the requests forwarded by the frontend. we define how HAProxy should route traffic to the backend servers and how it should manage these servers basically we specify also load balancing algorithms, and health checks. In summary we control how requests will be distributed among the servers and how we are going to handle server failures or maintenance.

<figure class="qs qt qu qv qw ml mr ms paragraph-image"><div class="nb nc fl nd bh ne" role="button" tabindex="0"><div class="mr ms qr"><picture><source sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" srcset="https://miro.medium.com/v2/resize:fit:640/format:webp/1*EiySIeb5Kzf6luig8UiIJw.png 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/1*EiySIeb5Kzf6luig8UiIJw.png 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/1*EiySIeb5Kzf6luig8UiIJw.png 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/1*EiySIeb5Kzf6luig8UiIJw.png 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/1*EiySIeb5Kzf6luig8UiIJw.png 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/1*EiySIeb5Kzf6luig8UiIJw.png 1100w, https://miro.medium.com/v2/resize:fit:1400/format:webp/1*EiySIeb5Kzf6luig8UiIJw.png 1400w" type="image/webp"><source data-testid="og" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" srcset="https://miro.medium.com/v2/resize:fit:640/1*EiySIeb5Kzf6luig8UiIJw.png 640w, https://miro.medium.com/v2/resize:fit:720/1*EiySIeb5Kzf6luig8UiIJw.png 720w, https://miro.medium.com/v2/resize:fit:750/1*EiySIeb5Kzf6luig8UiIJw.png 750w, https://miro.medium.com/v2/resize:fit:786/1*EiySIeb5Kzf6luig8UiIJw.png 786w, https://miro.medium.com/v2/resize:fit:828/1*EiySIeb5Kzf6luig8UiIJw.png 828w, https://miro.medium.com/v2/resize:fit:1100/1*EiySIeb5Kzf6luig8UiIJw.png 1100w, https://miro.medium.com/v2/resize:fit:1400/1*EiySIeb5Kzf6luig8UiIJw.png 1400w">![](https://miro.medium.com/v2/resize:fit:693/1*EiySIeb5Kzf6luig8UiIJw.png)</source></source></picture></div></div><figcaption class="ng ff nh mr ms ni nj bf b bg ab du" data-selectable-paragraph="">HA proxy load balancer</figcaption></figure></div></div>## I-2/Keepalived &amp; VRRP

**VRRP (Virtual Router Redundancy Protocol)** is designed to create a virtual router that represents a group of physical routers, allowing them to work together to present a single virtual IP address (VIP) to the network. This VIP is used as the default gateway by clients.

In a VRRP setup, one router is elected as the master. The master router handles traffic directed to the VIP, while the other routers in the group act as backups and monitor the master router’s health. If the master router fails, one of the backup routers takes over as the new master, ensuring the continuity of service.

**Keepalived** is a widely used implementation of VRRP with additional features. It assigns a priority to each node in the group, and based on these priorities, it elects a new master if a failure occurs. Keepalived enhances VRRP with advanced health checks and failover capabilities, making it ideal for high-availability setups.

<div class="ac cb" id="bkmrk-virtual-ip-assignmen" style="text-align: justify;"><div class="ci bh gb gc gd ge"><figure class="qs qt qu qv qw ml mr ms paragraph-image"><div class="mr ms qx"><picture><source sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 363px" srcset="https://miro.medium.com/v2/resize:fit:640/format:webp/1*NUi_6mEaRaIXi64siJ2yTQ.png 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/1*NUi_6mEaRaIXi64siJ2yTQ.png 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/1*NUi_6mEaRaIXi64siJ2yTQ.png 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/1*NUi_6mEaRaIXi64siJ2yTQ.png 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/1*NUi_6mEaRaIXi64siJ2yTQ.png 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/1*NUi_6mEaRaIXi64siJ2yTQ.png 1100w, https://miro.medium.com/v2/resize:fit:726/format:webp/1*NUi_6mEaRaIXi64siJ2yTQ.png 726w" type="image/webp"><source data-testid="og" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 363px" srcset="https://miro.medium.com/v2/resize:fit:640/1*NUi_6mEaRaIXi64siJ2yTQ.png 640w, https://miro.medium.com/v2/resize:fit:720/1*NUi_6mEaRaIXi64siJ2yTQ.png 720w, https://miro.medium.com/v2/resize:fit:750/1*NUi_6mEaRaIXi64siJ2yTQ.png 750w, https://miro.medium.com/v2/resize:fit:786/1*NUi_6mEaRaIXi64siJ2yTQ.png 786w, https://miro.medium.com/v2/resize:fit:828/1*NUi_6mEaRaIXi64siJ2yTQ.png 828w, https://miro.medium.com/v2/resize:fit:1100/1*NUi_6mEaRaIXi64siJ2yTQ.png 1100w, https://miro.medium.com/v2/resize:fit:726/1*NUi_6mEaRaIXi64siJ2yTQ.png 726w">![](https://miro.medium.com/v2/resize:fit:359/1*NUi_6mEaRaIXi64siJ2yTQ.png)</source></source></picture></div><figcaption class="ng ff nh mr ms ni nj bf b bg ab du" data-selectable-paragraph="">Virtual IP assignment</figcaption></figure></div></div># II-/ Deployment architecture

After understanding the foundational concepts of HAProxy and Keepalived, it’s crucial to see how these components come together to form a high availability cluster.

<div class="ac cb" id="bkmrk-deployment-architect" style="text-align: justify;"><div class="ci bh gb gc gd ge"><figure class="qs qt qu qv qw ml mr ms paragraph-image"><div class="nb nc fl nd bh ne" role="button" tabindex="0"><div class="mr ms qy"><picture><source sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" srcset="https://miro.medium.com/v2/resize:fit:640/format:webp/1*nSEqrpGJR60jh6Wd9d6UQw.png 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/1*nSEqrpGJR60jh6Wd9d6UQw.png 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/1*nSEqrpGJR60jh6Wd9d6UQw.png 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/1*nSEqrpGJR60jh6Wd9d6UQw.png 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/1*nSEqrpGJR60jh6Wd9d6UQw.png 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/1*nSEqrpGJR60jh6Wd9d6UQw.png 1100w, https://miro.medium.com/v2/resize:fit:1400/format:webp/1*nSEqrpGJR60jh6Wd9d6UQw.png 1400w" type="image/webp"><source data-testid="og" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" srcset="https://miro.medium.com/v2/resize:fit:640/1*nSEqrpGJR60jh6Wd9d6UQw.png 640w, https://miro.medium.com/v2/resize:fit:720/1*nSEqrpGJR60jh6Wd9d6UQw.png 720w, https://miro.medium.com/v2/resize:fit:750/1*nSEqrpGJR60jh6Wd9d6UQw.png 750w, https://miro.medium.com/v2/resize:fit:786/1*nSEqrpGJR60jh6Wd9d6UQw.png 786w, https://miro.medium.com/v2/resize:fit:828/1*nSEqrpGJR60jh6Wd9d6UQw.png 828w, https://miro.medium.com/v2/resize:fit:1100/1*nSEqrpGJR60jh6Wd9d6UQw.png 1100w, https://miro.medium.com/v2/resize:fit:1400/1*nSEqrpGJR60jh6Wd9d6UQw.png 1400w">![Deployment Architecture](https://miro.medium.com/v2/resize:fit:693/1*nSEqrpGJR60jh6Wd9d6UQw.png)</source></source></picture></div></div><figcaption class="ng ff nh mr ms ni nj bf b bg ab du" data-selectable-paragraph="">Deployment architecture</figcaption></figure></div></div>The architecture I’ve implemented leverages Docker to create a resilient and scalable environment, ensuring continuous service availability. The visual representation above illustrates how traffic is routed through HAProxy instances and managed by Keepalived to provide redundancy and failover capabilities.

To interconnect all components, I set up a Docker bridge network, which ensures seamless communication between the HAProxy instances, Keepalived, and the backend servers. This network allows the HAProxy instances to effectively distribute incoming traffic across multiple backend servers while monitoring their health and performance.

In this setup, there is a primary HAProxy instance (the master) and a secondary instance (the backup) ready to take over if the master fails. Keepalived, installed on both machines, manages the virtual IP (VIP) that clients connect to. This VIP ensures that, even in the event of a failure, traffic is automatically redirected to the backup HAProxy instance, maintaining service availability without interruption.

The backend comprises three cloned instances of a server running a simple Flask application that serves static content. This setup is an example of a stateless application deployment, where each instance operates independently without relying on session persistence or shared state. In the case of stateful applications, additional architectural considerations would be necessary, such as implementing shared storage, session replication, sticky sessions, or database clustering to ensure consistency and availability. Following best practices in system design is crucial to address these challenges and optimize the architecture based on the application’s specific requirements and context.

# III-/Step by step guide

<div class="ac cb" id="bkmrk-the-first-thing-we%E2%80%99r" style="text-align: justify;"><div class="ci bh gb gc gd ge">- The first thing we’re going to do is create our stateless app a simple Python application that doesn’t store any session information. To get started, we need to create a virtual environment, so make sure you have Python installed on your machine.

<figure class="qs qt qu qv qw ml mr ms paragraph-image"><div class="mr ms qz"><picture><source sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 600px" srcset="https://miro.medium.com/v2/resize:fit:640/format:webp/1*6rnOOSwuJMbZj5GIzMLPNA.png 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/1*6rnOOSwuJMbZj5GIzMLPNA.png 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/1*6rnOOSwuJMbZj5GIzMLPNA.png 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/1*6rnOOSwuJMbZj5GIzMLPNA.png 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/1*6rnOOSwuJMbZj5GIzMLPNA.png 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/1*6rnOOSwuJMbZj5GIzMLPNA.png 1100w, https://miro.medium.com/v2/resize:fit:1200/format:webp/1*6rnOOSwuJMbZj5GIzMLPNA.png 1200w" type="image/webp"><source data-testid="og" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 600px" srcset="https://miro.medium.com/v2/resize:fit:640/1*6rnOOSwuJMbZj5GIzMLPNA.png 640w, https://miro.medium.com/v2/resize:fit:720/1*6rnOOSwuJMbZj5GIzMLPNA.png 720w, https://miro.medium.com/v2/resize:fit:750/1*6rnOOSwuJMbZj5GIzMLPNA.png 750w, https://miro.medium.com/v2/resize:fit:786/1*6rnOOSwuJMbZj5GIzMLPNA.png 786w, https://miro.medium.com/v2/resize:fit:828/1*6rnOOSwuJMbZj5GIzMLPNA.png 828w, https://miro.medium.com/v2/resize:fit:1100/1*6rnOOSwuJMbZj5GIzMLPNA.png 1100w, https://miro.medium.com/v2/resize:fit:1200/1*6rnOOSwuJMbZj5GIzMLPNA.png 1200w">![](https://miro.medium.com/v2/resize:fit:594/1*6rnOOSwuJMbZj5GIzMLPNA.png)</source></source></picture></div><figcaption class="ng ff nh mr ms ni nj bf b bg ab du" data-selectable-paragraph="">Virtual environment creation</figcaption></figure>- Activate the virtual environment, and then install Flask.

<figure class="qs qt qu qv qw ml mr ms paragraph-image"><div class="mr ms ra"><picture><source sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 603px" srcset="https://miro.medium.com/v2/resize:fit:640/format:webp/1*G1V6zoscdsOMLl6EO-mhQw.png 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/1*G1V6zoscdsOMLl6EO-mhQw.png 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/1*G1V6zoscdsOMLl6EO-mhQw.png 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/1*G1V6zoscdsOMLl6EO-mhQw.png 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/1*G1V6zoscdsOMLl6EO-mhQw.png 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/1*G1V6zoscdsOMLl6EO-mhQw.png 1100w, https://miro.medium.com/v2/resize:fit:1206/format:webp/1*G1V6zoscdsOMLl6EO-mhQw.png 1206w" type="image/webp"><source data-testid="og" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 603px" srcset="https://miro.medium.com/v2/resize:fit:640/1*G1V6zoscdsOMLl6EO-mhQw.png 640w, https://miro.medium.com/v2/resize:fit:720/1*G1V6zoscdsOMLl6EO-mhQw.png 720w, https://miro.medium.com/v2/resize:fit:750/1*G1V6zoscdsOMLl6EO-mhQw.png 750w, https://miro.medium.com/v2/resize:fit:786/1*G1V6zoscdsOMLl6EO-mhQw.png 786w, https://miro.medium.com/v2/resize:fit:828/1*G1V6zoscdsOMLl6EO-mhQw.png 828w, https://miro.medium.com/v2/resize:fit:1100/1*G1V6zoscdsOMLl6EO-mhQw.png 1100w, https://miro.medium.com/v2/resize:fit:1206/1*G1V6zoscdsOMLl6EO-mhQw.png 1206w">![](https://miro.medium.com/v2/resize:fit:597/1*G1V6zoscdsOMLl6EO-mhQw.png)</source></source></picture></div><figcaption class="ng ff nh mr ms ni nj bf b bg ab du" data-selectable-paragraph="">Installing Flask</figcaption></figure>- Now Flask is installed in our virtual environment, we are going to create a simple Flask App with `hello world!`content, for me I’ll use nano editor, you can use whatever editor you want for that purpose.

</div></div>```
nano app.py
```

<div class="ac cb" id="bkmrk-flask-webapp" style="text-align: justify;"><div class="ci bh gb gc gd ge"><figure class="qs qt qu qv qw ml mr ms paragraph-image"><div class="mr ms rn"><picture><source sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 568px" srcset="https://miro.medium.com/v2/resize:fit:640/format:webp/1*WZlKOzXIcsWC8BSlwzMkkA.png 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/1*WZlKOzXIcsWC8BSlwzMkkA.png 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/1*WZlKOzXIcsWC8BSlwzMkkA.png 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/1*WZlKOzXIcsWC8BSlwzMkkA.png 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/1*WZlKOzXIcsWC8BSlwzMkkA.png 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/1*WZlKOzXIcsWC8BSlwzMkkA.png 1100w, https://miro.medium.com/v2/resize:fit:1136/format:webp/1*WZlKOzXIcsWC8BSlwzMkkA.png 1136w" type="image/webp"><source data-testid="og" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 568px" srcset="https://miro.medium.com/v2/resize:fit:640/1*WZlKOzXIcsWC8BSlwzMkkA.png 640w, https://miro.medium.com/v2/resize:fit:720/1*WZlKOzXIcsWC8BSlwzMkkA.png 720w, https://miro.medium.com/v2/resize:fit:750/1*WZlKOzXIcsWC8BSlwzMkkA.png 750w, https://miro.medium.com/v2/resize:fit:786/1*WZlKOzXIcsWC8BSlwzMkkA.png 786w, https://miro.medium.com/v2/resize:fit:828/1*WZlKOzXIcsWC8BSlwzMkkA.png 828w, https://miro.medium.com/v2/resize:fit:1100/1*WZlKOzXIcsWC8BSlwzMkkA.png 1100w, https://miro.medium.com/v2/resize:fit:1136/1*WZlKOzXIcsWC8BSlwzMkkA.png 1136w">![](https://miro.medium.com/v2/resize:fit:562/1*WZlKOzXIcsWC8BSlwzMkkA.png)</source></source></picture></div><figcaption class="ng ff nh mr ms ni nj bf b bg ab du" data-selectable-paragraph="">Flask webapp</figcaption></figure></div></div>Copy paste the following content or create your own:

```
from flask import Flask<br></br>app = Flask(__name__)<br></br><br></br>@app.route('/')<br></br>def hello_world():<br></br>    return 'Hello, World!'<br></br><br></br>if __name__ == '__main__':<br></br>    app.run(debug=False)
```

Flask apps run by default in port 5000, you can test the webapp by running: `python app.py`

<div class="ac cb" id="bkmrk-running-flask-app-ac" style="text-align: justify;"><div class="ci bh gb gc gd ge"><figure class="qs qt qu qv qw ml mr ms paragraph-image"><div class="nb nc fl nd bh ne" role="button" tabindex="0"><div class="mr ms ro"><picture><source sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" srcset="https://miro.medium.com/v2/resize:fit:640/format:webp/1*LZofDS6Y0H3OKhPDGWEtYg.png 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/1*LZofDS6Y0H3OKhPDGWEtYg.png 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/1*LZofDS6Y0H3OKhPDGWEtYg.png 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/1*LZofDS6Y0H3OKhPDGWEtYg.png 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/1*LZofDS6Y0H3OKhPDGWEtYg.png 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/1*LZofDS6Y0H3OKhPDGWEtYg.png 1100w, https://miro.medium.com/v2/resize:fit:1400/format:webp/1*LZofDS6Y0H3OKhPDGWEtYg.png 1400w" type="image/webp"><source data-testid="og" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" srcset="https://miro.medium.com/v2/resize:fit:640/1*LZofDS6Y0H3OKhPDGWEtYg.png 640w, https://miro.medium.com/v2/resize:fit:720/1*LZofDS6Y0H3OKhPDGWEtYg.png 720w, https://miro.medium.com/v2/resize:fit:750/1*LZofDS6Y0H3OKhPDGWEtYg.png 750w, https://miro.medium.com/v2/resize:fit:786/1*LZofDS6Y0H3OKhPDGWEtYg.png 786w, https://miro.medium.com/v2/resize:fit:828/1*LZofDS6Y0H3OKhPDGWEtYg.png 828w, https://miro.medium.com/v2/resize:fit:1100/1*LZofDS6Y0H3OKhPDGWEtYg.png 1100w, https://miro.medium.com/v2/resize:fit:1400/1*LZofDS6Y0H3OKhPDGWEtYg.png 1400w">![](https://miro.medium.com/v2/resize:fit:693/1*LZofDS6Y0H3OKhPDGWEtYg.png)</source></source></picture></div></div><figcaption class="ng ff nh mr ms ni nj bf b bg ab du" data-selectable-paragraph="">Running Flask app</figcaption></figure><figure class="qs qt qu qv qw ml mr ms paragraph-image"><div class="nb nc fl nd bh ne" role="button" tabindex="0"><div class="mr ms rp"><picture><source sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" srcset="https://miro.medium.com/v2/resize:fit:640/format:webp/1*3RydS1s3SacPyZndJsdEFQ.png 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/1*3RydS1s3SacPyZndJsdEFQ.png 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/1*3RydS1s3SacPyZndJsdEFQ.png 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/1*3RydS1s3SacPyZndJsdEFQ.png 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/1*3RydS1s3SacPyZndJsdEFQ.png 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/1*3RydS1s3SacPyZndJsdEFQ.png 1100w, https://miro.medium.com/v2/resize:fit:1400/format:webp/1*3RydS1s3SacPyZndJsdEFQ.png 1400w" type="image/webp"><source data-testid="og" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" srcset="https://miro.medium.com/v2/resize:fit:640/1*3RydS1s3SacPyZndJsdEFQ.png 640w, https://miro.medium.com/v2/resize:fit:720/1*3RydS1s3SacPyZndJsdEFQ.png 720w, https://miro.medium.com/v2/resize:fit:750/1*3RydS1s3SacPyZndJsdEFQ.png 750w, https://miro.medium.com/v2/resize:fit:786/1*3RydS1s3SacPyZndJsdEFQ.png 786w, https://miro.medium.com/v2/resize:fit:828/1*3RydS1s3SacPyZndJsdEFQ.png 828w, https://miro.medium.com/v2/resize:fit:1100/1*3RydS1s3SacPyZndJsdEFQ.png 1100w, https://miro.medium.com/v2/resize:fit:1400/1*3RydS1s3SacPyZndJsdEFQ.png 1400w">![](https://miro.medium.com/v2/resize:fit:693/1*3RydS1s3SacPyZndJsdEFQ.png)</source></source></picture></div></div><figcaption class="ng ff nh mr ms ni nj bf b bg ab du" data-selectable-paragraph="">Accessing web app on the browser</figcaption></figure></div></div>The app is up and running Now we should get the dependencies used in our app, we will save them on requirements.txt .

<div class="ac cb" id="bkmrk-saving-requirements-" style="text-align: justify;"><div class="ci bh gb gc gd ge"><figure class="qs qt qu qv qw ml mr ms paragraph-image"><div class="mr ms rq"><picture><source sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 580px" srcset="https://miro.medium.com/v2/resize:fit:640/format:webp/1*18q0XhVH-PcLfuj2ZrUwgw.png 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/1*18q0XhVH-PcLfuj2ZrUwgw.png 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/1*18q0XhVH-PcLfuj2ZrUwgw.png 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/1*18q0XhVH-PcLfuj2ZrUwgw.png 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/1*18q0XhVH-PcLfuj2ZrUwgw.png 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/1*18q0XhVH-PcLfuj2ZrUwgw.png 1100w, https://miro.medium.com/v2/resize:fit:1160/format:webp/1*18q0XhVH-PcLfuj2ZrUwgw.png 1160w" type="image/webp"><source data-testid="og" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 580px" srcset="https://miro.medium.com/v2/resize:fit:640/1*18q0XhVH-PcLfuj2ZrUwgw.png 640w, https://miro.medium.com/v2/resize:fit:720/1*18q0XhVH-PcLfuj2ZrUwgw.png 720w, https://miro.medium.com/v2/resize:fit:750/1*18q0XhVH-PcLfuj2ZrUwgw.png 750w, https://miro.medium.com/v2/resize:fit:786/1*18q0XhVH-PcLfuj2ZrUwgw.png 786w, https://miro.medium.com/v2/resize:fit:828/1*18q0XhVH-PcLfuj2ZrUwgw.png 828w, https://miro.medium.com/v2/resize:fit:1100/1*18q0XhVH-PcLfuj2ZrUwgw.png 1100w, https://miro.medium.com/v2/resize:fit:1160/1*18q0XhVH-PcLfuj2ZrUwgw.png 1160w">![](https://miro.medium.com/v2/resize:fit:574/1*18q0XhVH-PcLfuj2ZrUwgw.png)</source></source></picture></div><figcaption class="ng ff nh mr ms ni nj bf b bg ab du" data-selectable-paragraph="">Saving requirements in a file</figcaption></figure></div></div>```
pip freeze > requirements.txt
```

\- We will need these requirements so we can create the Dockerfile of our webapp, then run the following command to create a Dockerfile of the webapp.

```
nano Dockerfile.webapp
```

copy and paste the following:

```
<br></br>FROM python:3.12.5<br></br>Expose 5000<br></br>ENV FLASK_app=app.py<br></br>WORKDIR /app<br></br>COPY ./app.py .<br></br>COPY ./requirements.txt .<br></br>RUN pip install -r requirements.txt<br></br>RUN pip install gunicorn<br></br>CMD gunicorn -w 4 -b :5000 app:app
```

Note that we added Gunicorn in our Dockerfile to run our Python web app because using development servers is not suitable for production environments.

Let’s create our first image , it’ll be saved locally.

```
docker build -f Dockerfile.webapp -t webapp_test .
```

After finalizing the Dockerfile for our web app, it’s time to create our Docker Compose YAML file, which will define our entire architecture. We’ll break it down step by step.

Here is the docker compose file.

```
version: '3.8'<br></br><br></br>services:<br></br>  haproxy1:<br></br>    build:<br></br>      context: .<br></br>      dockerfile: Dockerfile<br></br>    container_name: haproxy1<br></br>    networks:<br></br>      yahya_prive:<br></br>        ipv4_address: 10.0.0.150<br></br>    cap_add:<br></br>      - NET_ADMIN<br></br>    ports:<br></br>      - "8888:80"<br></br>      - "8404:8404"<br></br>    volumes:<br></br>      - C:\Users\John macmillan\Desktop\python_project\haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro<br></br>      - C:\Users\John macmillan\Desktop\python_project\keepalived_primary.conf:/etc/keepalived/keepalived.conf:ro<br></br>    depends_on:<br></br>      - web1<br></br>      - web2<br></br>      - web3<br></br>    entrypoint: ["/bin/sh", "-c", "keepalived -D -f /etc/keepalived/keepalived.conf && haproxy -f /usr/local/etc/haproxy/haproxy.cfg"]<br></br><br></br>  haproxy2:<br></br>    build:<br></br>      context: .<br></br>      dockerfile: Dockerfile<br></br>    container_name: haproxy2<br></br>    networks:<br></br>      yahya_prive:<br></br>        ipv4_address: 10.0.0.155<br></br>    cap_add:<br></br>      - NET_ADMIN<br></br>    ports:<br></br>      - "8800:80"<br></br>      - "8405:8404"<br></br>    volumes:<br></br>      - C:\Users\John macmillan\Desktop\python_project\haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro<br></br>      - C:\Users\John macmillan\Desktop\python_project\keepalived_bck.conf:/etc/keepalived/keepalived.conf:ro<br></br>    depends_on:<br></br>      - web1<br></br>      - web2<br></br>      - web3<br></br>    entrypoint: ["/bin/sh", "-c", "keepalived -D -f /etc/keepalived/keepalived.conf && haproxy -f /usr/local/etc/haproxy/haproxy.cfg"]<br></br><br></br>  web1:<br></br>    image: webapp_test<br></br>    container_name: web1<br></br>    networks:<br></br>      - yahya_prive<br></br><br></br>  web2:<br></br>    image: webapp_test<br></br>    container_name: web2<br></br>    networks:<br></br>      - yahya_prive<br></br><br></br>  web3:<br></br>    image: webapp_test<br></br>    container_name: web3<br></br>    networks:<br></br>      - yahya_prive<br></br><br></br>networks:<br></br>  yahya_prive:<br></br>   driver: bridge<br></br>   #specify the driver <br></br>   ipam:  <br></br>    config : <br></br>       - subnet: 10.0.0.0/24<br></br>         gateway: 10.0.0.1
```

let’s break this down:

First thing we did is to create a network bridge . This Network bridge is called `yahya_prive`then we specified the CIDR notation: `10.0.0.0/24`with the following gateway: `10.0.0.1`

it’ll be our private network in which all the containers will be assigned an ip address from the ip address range 10.0.0.0/24 .

```
networks:<br></br>  yahya_prive:<br></br>   driver: bridge<br></br>   #specify the driver <br></br>   ipam:  <br></br>    config : <br></br>       - subnet: 10.0.0.0/24<br></br>         gateway: 10.0.0.1
```

It’s advisable to use a custom network rather than the default network provided by Docker Compose. This approach enhances security and allows you to use domain names instead of IP addresses in configurations.

Then there is a section for our webapps, they’re called respectively: web1, web2 and web3, all three of them is now attached to `yahya_prive`network.

Each one was provided a name, and the base image was created previously, and it was saved locally which is `webapp_test`

```
web1:<br></br>    image: webapp_test<br></br>    container_name: web1<br></br>    networks:<br></br>      - yahya_prive<br></br><br></br>  web2:<br></br>    image: webapp_test<br></br>    container_name: web2<br></br>    networks:<br></br>      - yahya_prive<br></br><br></br>  web3:<br></br>    image: webapp_test<br></br>    container_name: web3<br></br>    networks:<br></br>      - yahya_prive
```

After this we’ve created the service for our HAProxy load balancer.

```
haproxy1:<br></br>    build:<br></br>      context: .<br></br>      dockerfile: Dockerfile<br></br>    container_name: haproxy1<br></br>    networks:<br></br>      yahya_prive:<br></br>        ipv4_address: 10.0.0.150<br></br>    cap_add:<br></br>      - NET_ADMIN<br></br>    ports:<br></br>      - "8888:80"<br></br>      - "8404:8404"<br></br>    volumes:<br></br>      - C:\Users\John macmillan\Desktop\python_project\haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro<br></br>      - C:\Users\John macmillan\Desktop\python_project\keepalived_primary.conf:/etc/keepalived/keepalived.conf:ro<br></br>    depends_on:<br></br>      - web1<br></br>      - web2<br></br>      - web3<br></br>    entrypoint: ["/bin/sh", "-c", "keepalived -D -f /etc/keepalived/keepalived.conf && haproxy -f /usr/local/etc/haproxy/haproxy.cfg"]
```

The Master HAProxy is called haproxy1, and it was assigned the ip address:

`10.0.0.150`from the private network yahya\_prive.

To enable Keepalived, which uses VRRP for failover, the container requires additional network capabilities. Therefore, we grant the container `NET_ADMIN` privileges, allowing it to manage network settings necessary for VRRP operations.

The necessary configuration files for HAProxy and Keepalived are mounted as read-only volumes from the host machine to ensure that both services are properly configured.

The `entrypoint` directive ensures that Keepalived starts in the background and monitors the HAProxy service, providing the high availability setup.

And obviously, there is the configuration `Dockerfile` that contains our Keepalived and HAProxy.

```
FROM ubuntu:22.04<br></br><br></br># Install Keepalived and HAProxy<br></br>RUN apt-get update && apt-get install -y \<br></br>    nano \<br></br> net-tools \<br></br>    keepalived \<br></br>    haproxy<br></br> <br></br>EXPOSE 80<br></br>EXPOSE 8444<br></br>
```

The configuration for both load balancers is exactly the same (`haproxy.cfg`)

```
global<br></br>  stats socket /var/run/api.sock user haproxy group haproxy mode 660 level admin expose-fd listeners<br></br>  log stdout format raw local0 info<br></br><br></br>defaults<br></br>  mode http<br></br>  timeout client 10s<br></br>  timeout connect 5s<br></br>  timeout server 10s<br></br>  timeout http-request 10s<br></br>  log global<br></br><br></br>frontend stats<br></br>  bind *:8404<br></br>  stats enable<br></br>  stats uri /<br></br>  stats refresh 10s<br></br><br></br>frontend myfrontend<br></br>  bind :80<br></br>  default_backend webservers<br></br><br></br>backend webservers<br></br>server s1 web1:5000 check<br></br>server s2 web2:5000 check<br></br>server s3 web3:5000 check
```

What we’ve done here is configure our load balancer to listen for requests on port 80. We’ve also defined our web servers in the backend webservers section, allowing us to route traffic to them effectively.

On the other hand, we had different configurations of Keepalived daemons for both nodes.

here is the configuration for the master node (`keepalived_primary.conf`):

```
vrrp_instance VI_1 {<br></br>    state MASTER<br></br>    interface eth0<br></br>    virtual_router_id 33<br></br>    priority 255<br></br>    advert_int 1<br></br>    unicast_src_ip 10.0.0.50<br></br>    <br></br>    authentication {<br></br>        auth_type PASS<br></br>        auth_pass letmein<br></br>    }<br></br>    <br></br>    virtual_ipaddress {<br></br>        10.0.0.50/24 dev eth0<br></br>    }  <br></br>}
```

This is a basic configuration where we’ve assigned a priority of 255, which is higher than the priority set on the backup machine. As a result, this machine will be assigned the virtual IP address first. We’ve defined the state as `MASTER` for this machine and specified `eth0` as the interface, as it’s connected to the private network through this interface.

The most critical part of this configuration is the `virtual_ipaddress` section, where we define the virtual IP address (`10.0.0.50/24`) that will be managed by the VRRP protocol. This IP address will be assigned to the master machine, ensuring that it handles traffic as long as it remains in the master state. The `authentication` section provides basic security by requiring a password for VRRP communications, adding an extra layer of protection to the setup.

The configuration for the backup node will be different a little bit (`keepalived_bck.conf`):

```
vrrp_instance VI_1 {<br></br>    state BACKUP<br></br>    interface eth0<br></br>    virtual_router_id 33<br></br>    priority 150<br></br>    advert_int 1<br></br>    unicast_src_ip 10.0.0.50<br></br>    <br></br>    authentication {<br></br>        auth_type PASS<br></br>        auth_pass letmein<br></br>    }<br></br>    <br></br>    virtual_ipaddress {<br></br>        10.0.0.50/24 dev eth0<br></br>    }  <br></br>}
```

We’ve set a priority of 150, which is lower than that of the master, and designated the state as `BACKUP`. Both the master and backup nodes share the same virtual IP address (VIP). In the event of the master node failing, the backup node will detect this through ARP checks and automatically take over the VIP, ensuring continued service availability.

Note that the Docker Compose file includes a `depends_on` section that specifies the order in which containers are started. In this case:

```
depends_on:<br></br>      - web1<br></br>      - web2<br></br>      - web3
```

This configuration ensures that the web applications (`web1`, `web2`, and `web3`) start before the load balancers are initialized. This order is crucial to ensure that the web servers are up and running before the load balancers begin routing traffic to them.

After completing the necessary configurations, you can run the entire setup with the following command:

```
docker-compose -p high_availability_cluster up -d
```

Now, your setup is up and running.

<div class="ac cb" id="bkmrk-accessing-webapp" style="text-align: justify;"><div class="ci bh gb gc gd ge"><figure class="qs qt qu qv qw ml mr ms paragraph-image"><div class="nb nc fl nd bh ne" role="button" tabindex="0"><div class="mr ms rr"><picture><source sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" srcset="https://miro.medium.com/v2/resize:fit:640/format:webp/1*aTZ2S3WfdfYJcsNjnjSpsg.png 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/1*aTZ2S3WfdfYJcsNjnjSpsg.png 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/1*aTZ2S3WfdfYJcsNjnjSpsg.png 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/1*aTZ2S3WfdfYJcsNjnjSpsg.png 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/1*aTZ2S3WfdfYJcsNjnjSpsg.png 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/1*aTZ2S3WfdfYJcsNjnjSpsg.png 1100w, https://miro.medium.com/v2/resize:fit:1400/format:webp/1*aTZ2S3WfdfYJcsNjnjSpsg.png 1400w" type="image/webp"><source data-testid="og" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" srcset="https://miro.medium.com/v2/resize:fit:640/1*aTZ2S3WfdfYJcsNjnjSpsg.png 640w, https://miro.medium.com/v2/resize:fit:720/1*aTZ2S3WfdfYJcsNjnjSpsg.png 720w, https://miro.medium.com/v2/resize:fit:750/1*aTZ2S3WfdfYJcsNjnjSpsg.png 750w, https://miro.medium.com/v2/resize:fit:786/1*aTZ2S3WfdfYJcsNjnjSpsg.png 786w, https://miro.medium.com/v2/resize:fit:828/1*aTZ2S3WfdfYJcsNjnjSpsg.png 828w, https://miro.medium.com/v2/resize:fit:1100/1*aTZ2S3WfdfYJcsNjnjSpsg.png 1100w, https://miro.medium.com/v2/resize:fit:1400/1*aTZ2S3WfdfYJcsNjnjSpsg.png 1400w">![Accessing webapp](https://miro.medium.com/v2/resize:fit:693/1*aTZ2S3WfdfYJcsNjnjSpsg.png)</source></source></picture></div></div><figcaption class="ng ff nh mr ms ni nj bf b bg ab du" data-selectable-paragraph="">Accessing webapp</figcaption></figure></div></div>The `haproxy1` container will receive the virtual IP address on its `eth0` interface. You can verify this by running the following command inside the container: `ip a`.

<div class="ac cb" id="bkmrk-virtual-ip-address-a" style="text-align: justify;"><div class="ci bh gb gc gd ge"><figure class="qs qt qu qv qw ml mr ms paragraph-image"><div class="nb nc fl nd bh ne" role="button" tabindex="0"><div class="mr ms rs"><picture><source sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" srcset="https://miro.medium.com/v2/resize:fit:640/format:webp/1*L_B3ZgQXT-ikW5_RaSxzyA.png 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/1*L_B3ZgQXT-ikW5_RaSxzyA.png 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/1*L_B3ZgQXT-ikW5_RaSxzyA.png 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/1*L_B3ZgQXT-ikW5_RaSxzyA.png 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/1*L_B3ZgQXT-ikW5_RaSxzyA.png 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/1*L_B3ZgQXT-ikW5_RaSxzyA.png 1100w, https://miro.medium.com/v2/resize:fit:1400/format:webp/1*L_B3ZgQXT-ikW5_RaSxzyA.png 1400w" type="image/webp"><source data-testid="og" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" srcset="https://miro.medium.com/v2/resize:fit:640/1*L_B3ZgQXT-ikW5_RaSxzyA.png 640w, https://miro.medium.com/v2/resize:fit:720/1*L_B3ZgQXT-ikW5_RaSxzyA.png 720w, https://miro.medium.com/v2/resize:fit:750/1*L_B3ZgQXT-ikW5_RaSxzyA.png 750w, https://miro.medium.com/v2/resize:fit:786/1*L_B3ZgQXT-ikW5_RaSxzyA.png 786w, https://miro.medium.com/v2/resize:fit:828/1*L_B3ZgQXT-ikW5_RaSxzyA.png 828w, https://miro.medium.com/v2/resize:fit:1100/1*L_B3ZgQXT-ikW5_RaSxzyA.png 1100w, https://miro.medium.com/v2/resize:fit:1400/1*L_B3ZgQXT-ikW5_RaSxzyA.png 1400w">![](https://miro.medium.com/v2/resize:fit:693/1*L_B3ZgQXT-ikW5_RaSxzyA.png)</source></source></picture></div></div><figcaption class="ng ff nh mr ms ni nj bf b bg ab du" data-selectable-paragraph="">Virtual Ip address assigned</figcaption></figure></div></div>We can try sending an HTTP request using the `curl` command from a node inside the `yahya_prive` network. For example, we can use `web1` for this purpose.

<div class="ac cb" id="bkmrk-getting-web-content-" style="text-align: justify;"><div class="ci bh gb gc gd ge"><figure class="qs qt qu qv qw ml mr ms paragraph-image"><div class="mr ms rt"><picture><source sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 693px" srcset="https://miro.medium.com/v2/resize:fit:640/format:webp/1*IyOws179aa5Hu5lS8D_DOA.png 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/1*IyOws179aa5Hu5lS8D_DOA.png 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/1*IyOws179aa5Hu5lS8D_DOA.png 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/1*IyOws179aa5Hu5lS8D_DOA.png 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/1*IyOws179aa5Hu5lS8D_DOA.png 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/1*IyOws179aa5Hu5lS8D_DOA.png 1100w, https://miro.medium.com/v2/resize:fit:1386/format:webp/1*IyOws179aa5Hu5lS8D_DOA.png 1386w" type="image/webp"><source data-testid="og" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 693px" srcset="https://miro.medium.com/v2/resize:fit:640/1*IyOws179aa5Hu5lS8D_DOA.png 640w, https://miro.medium.com/v2/resize:fit:720/1*IyOws179aa5Hu5lS8D_DOA.png 720w, https://miro.medium.com/v2/resize:fit:750/1*IyOws179aa5Hu5lS8D_DOA.png 750w, https://miro.medium.com/v2/resize:fit:786/1*IyOws179aa5Hu5lS8D_DOA.png 786w, https://miro.medium.com/v2/resize:fit:828/1*IyOws179aa5Hu5lS8D_DOA.png 828w, https://miro.medium.com/v2/resize:fit:1100/1*IyOws179aa5Hu5lS8D_DOA.png 1100w, https://miro.medium.com/v2/resize:fit:1386/1*IyOws179aa5Hu5lS8D_DOA.png 1386w">![](https://miro.medium.com/v2/resize:fit:686/1*IyOws179aa5Hu5lS8D_DOA.png)</source></source></picture></div><figcaption class="ng ff nh mr ms ni nj bf b bg ab du" data-selectable-paragraph="">Getting web content from using VIP</figcaption></figure></div></div># IV-/Conclusion

In conclusion, building a high availability cluster with HAProxy, Keepalived, and Docker is an effective way to ensure continuous service availability and reliability. Through this guide, we’ve explored the fundamental concepts of high availability, examined how HAProxy and Keepalived work together to manage traffic and failover, and demonstrated how to set up this architecture in a Docker environment. By following these steps, you can create a resilient infrastructure that can handle disruptions and maintain service continuity, making it an essential setup for any robust and scalable application deployment.

**Equipped with this understanding, you’ll now be able to enhance your network infrastructure and deploy more resilient, scalable applications with confidence.**

**We invite you to share your experiences and insights in the comments below. We’re eager to hear your feedback and thoughts. Happy networking!**

**References**

For more detailed information :

<div class="ac cb" id="bkmrk-networks-top-level-e"><div class="ci bh gb gc gd ge">1. [Networks top-level elements | Docker Docs](https://docs.docker.com/reference/compose-file/networks/)
2. [HAProxy version 2.9.9–41 — Starter Guide](https://docs.haproxy.org/2.9/intro.html)
3. [Understanding Virtual Router Redundancy Protocol (VRRP) | FS Community](https://community.fs.com/article/understanding-virtual-router-redundancy-protocol-vrrp.html)
4. [Setting up a Linux cluster with Keepalived: Basic configuration | Enable Sysadmin (redhat.com)](https://www.redhat.com/sysadmin/keepalived-basics)

</div></div>