<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[My deployment]]></title><description><![CDATA[My deployment]]></description><link>https://tekvanguard.hashnode.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1593680282896/kNC7E8IR4.png</url><title>My deployment</title><link>https://tekvanguard.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Mon, 21 Sep 2026 07:32:30 GMT</lastBuildDate><atom:link href="https://tekvanguard.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[I Built a Cloud Application. Then I Asked: What Happens When 10,000 Users Show Up?]]></title><description><![CDATA[Scaling an application is not just about adding more servers. It is about designing for traffic, failure, performance, availability, and growth.
The application worked.
That was the exciting part.
The]]></description><link>https://tekvanguard.hashnode.dev/i-built-a-cloud-application-then-i-asked-what-happens-when-10-000-users-show-up</link><guid isPermaLink="true">https://tekvanguard.hashnode.dev/i-built-a-cloud-application-then-i-asked-what-happens-when-10-000-users-show-up</guid><category><![CDATA[AWS]]></category><category><![CDATA[Cloud Computing]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[architecture]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[ijeoma ajah]]></dc:creator><pubDate>Thu, 13 Aug 2026 07:52:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a3b2cdac3746f347e362fe5/3b2ec653-28a1-46cd-8cb4-1f29f33a4811.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p> <em>Scaling an application is not just about adding more servers. It is about designing for traffic, failure, performance, availability, and growth.</em></p>
<p>The application worked.</p>
<p>That was the exciting part.</p>
<p>The server was running. The application was accessible from the internet. Requests were reaching the backend, the database was responding, and everything looked fine.</p>
<p>For a while, that felt like success.</p>
<p>Then I asked myself a question that changed the way I started looking at cloud engineering:</p>
<p>What happens when 10,000 people try to use this application at the same time?</p>
<p>The answer is not simply, “Get a bigger server.”</p>
<p>That might work for a while, but it does not solve every problem.</p>
<p>What happens when the server crashes?</p>
<p>What happens when traffic suddenly increases?</p>
<p>What happens when the database becomes the bottleneck? </p>
<p>What happens when users are accessing the application from different parts of the world?</p>
<p>What happens when we deploy a new version and something breaks?</p>
<p>And perhaps most importantly, how do we know that something is going wrong before our users start complaining?</p>
<p>These questions took me beyond the simple idea of putting an application on a cloud server.</p>
<p>They introduced me to something much more important.</p>
<p><strong>Architecture.</strong></p>
<p>Building an application that works is one thing.</p>
<p>Building an application that can handle growth, survive failures, maintain reasonable performance, and be monitored and updated reliably is a completely different challenge.</p>
<p>That is where Cloud Engineering and DevOps start becoming much more interesting.</p>
<p>Before We Scale, Let Us Start with Something Simple</p>
<p>Imagine our application has three basic parts.</p>
<p>The frontend is what the user interacts with.</p>
<p>The backend contains the application logic.</p>
<p>The database stores the information the application needs.</p>
<p>At the beginning, everything could technically run on one server.</p>
<p>It might look something like this:  <strong>User → Server → Database</strong></p>
<p>For a small project, this may be perfectly reasonable.</p>
<p>There is nothing wrong with starting small.</p>
<p>In fact, starting with a simple architecture can be a good decision when the application is still small and the requirements are straightforward.</p>
<p>The problem begins when we assume that the same architecture will continue to work regardless of how much the application grows.</p>
<p>A server has limits.</p>
<p>It has a certain amount of CPU.</p>
<p>It has a certain amount of memory.</p>
<p>It has a certain network capacity.</p>
<p>It can only process so many requests at a time.</p>
<p>Eventually, we reach a point where the server starts struggling.</p>
<p>The application becomes slower.</p>
<p>Requests take longer to complete.</p>
<p>Users begin experiencing errors.</p>
<p>And if the server completely fails, the entire application could become unavailable.</p>
<p>That creates another problem.</p>
<p>The server has become a single point of failure.</p>
<p>So instead of asking how to make one server do everything, we can start asking a better question:</p>
<p>How can we design the system so that the workload can be distributed?</p>
<p>That question leads us to our next component.</p>
<p><strong>What Happens When One Server Is Not Enough?</strong> </p>
<p>There are two common ways to approach this problem.</p>
<p>The first is <strong>vertical</strong> <strong>scaling</strong>.</p>
<p>We make the existing server bigger.</p>
<p>We give it more CPU, more memory, more storage, or other resources.</p>
<p>This can be useful, especially when the application is still relatively small.</p>
<p>But there is a limit to how far we can go.</p>
<p>A bigger server can also become increasingly expensive.</p>
<p>Eventually, we may be paying a lot of money for one machine that is still a single point of failure.</p>
<p>The second approach is <strong>horizontal</strong> <strong>scaling</strong>.</p>
<p>Instead of making one server bigger, we add more servers.</p>
<p>For example, we could move from one application server to three.</p>
<p>Now we have:</p>
<p><strong>User → Server 1<br />User → Server 2<br />User → Server 3</strong></p>
<p> But this creates another question.</p>
<p><strong>How does the user know which server to connect to?</strong></p>
<p>We do not want users manually choosing servers.</p>
<p>We need something in front of those servers that can receive incoming requests and distribute them appropriately.</p>
<p>This is where a load balancer becomes useful.</p>
<p> <strong>The Load Balancer Becomes the Traffic Controller</strong></p>
<p>Think about a busy restaurant.</p>
<p>If there is only one waiter and fifty customers arrive at almost the same time, that waiter is going to have a difficult day.</p>
<p>Now imagine there are five waiters.</p>
<p>The customers can be distributed among them.</p>
<p>The same basic idea applies to a load balancer.</p>
<p>Instead of users connecting directly to one application server, they connect to the load balancer.</p>
<p>The load balancer receives the incoming requests and distributes them across available servers.</p>
<p>Our architecture now looks more like this:</p>
<p><strong>User → Load Balancer → Server 1<br />→ Server 2<br />→ Server 3</strong></p>
<p> This immediately gives us an advantage.</p>
<p>The workload can be distributed.</p>
<p>If Server 1 is busy, other servers can handle requests.</p>
<p>If Server 1 becomes unhealthy, the load balancer can stop sending traffic to it while the other servers continue serving users.</p>
<p>In AWS, Elastic Load Balancing provides this type of traffic distribution.</p>
<p>But we still have a problem.</p>
<p> <strong>What happens when our three servers are no longer enough?</strong></p>
<p>What If Traffic Keeps Increasing?</p>
<p>Suppose our application starts with three servers.</p>
<p>Everything is working well.</p>
<p>Then our application becomes popular.</p>
<p>Traffic increases.</p>
<p>Our servers are now operating at very high capacity.</p>
<p>We could manually create more servers.</p>
<p>But that does not sound particularly efficient.</p>
<p>Imagine having someone monitoring server usage throughout the night and manually launching new servers whenever traffic increases.</p>
<p>One of the reasons cloud computing is useful is that we can automate this kind of process.</p>
<p> <strong>This is where Auto Scaling becomes important.</strong></p>
<p>With an Auto Scaling setup, we can define rules that determine when additional compute resources should be added or removed.</p>
<p>For example, if the workload remains high for a certain period, additional servers can be launched.</p>
<p>When demand drops, unnecessary servers can be removed.</p>
<p>The exact configuration depends on the application and its requirements, but the underlying idea is simple.</p>
<p>Resources should be able to respond to demand.</p>
<p>During periods of high traffic, we may need more capacity.</p>
<p>During periods of low traffic, we may need less.</p>
<p>This allows infrastructure to become more flexible.</p>
<p>Instead of building everything around the highest amount of traffic we might ever receive, we can design an environment that can adjust as demand changes.</p>
<p>But there is still another major part of the system we need to think about.</p>
<p> <strong>The database.</strong></p>
<p>The Database Can Become the Bottleneck</p>
<p>Our application servers may be doing well.</p>
<p>We have three servers.</p>
<p>We have a load balancer.</p>
<p>We have Auto Scaling.</p>
<p>Everything looks great.</p>
<p>Then we discover that the application is still slow.</p>
<p>Why?</p>
<p>The database.</p>
<p>Every application request that needs data has to communicate with the database.</p>
<p>Users are logging in.</p>
<p>Orders are being created.</p>
<p>Products are being retrieved.</p>
<p>Messages are being stored.</p>
<p>Transactions are being processed.</p>
<p>If all those requests eventually reach one database that cannot handle the workload, the database becomes the bottleneck.</p>
<p>Adding more application servers will not automatically solve that problem.</p>
<p>This is one of the things I find interesting about cloud architecture.</p>
<p>You cannot simply look at one component in isolation.</p>
<p>You have to understand how the components depend on one another.</p>
<p>A fast application server does not help much if it spends most of its time waiting for a slow database.</p>
<p><strong>Using a Managed Database</strong></p>
<p>Instead of installing and managing a database ourselves on an ordinary server, we can use a managed database service.</p>
<p><strong>Amazon</strong> <strong>RDS</strong> is one example.</p>
<p>A managed service does not mean that we no longer have responsibilities.</p>
<p>We still need to think about the database engine, instance size, storage, security, backups, access permissions, performance, and other configuration decisions.</p>
<p>However, many of the underlying administrative responsibilities can be handled by the cloud provider.</p>
<p>That can allow an engineering team to spend more time focusing on the application itself.</p>
<p>But there is an important lesson here.</p>
<p>A database is not simply somewhere we put data.</p>
<p>It is a critical part of the application architecture.</p>
<p>If the database becomes unavailable, the application may become unavailable too.</p>
<p>That means reliability and recovery have to be considered from the beginning.</p>
<p> <strong>What Happens When Users Are Far Away?</strong></p>
<p>Now imagine that our application becomes popular outside our original target location.</p>
<p>We have users in Nigeria.</p>
<p>We have users in the United States.</p>
<p>We have users in India.</p>
<p>We have users in Australia.</p>
<p>All of them are accessing infrastructure that may be physically far away from them.</p>
<p>The internet is incredibly capable, but physical distance still matters when it comes to latency.</p>
<p> <strong>This is where a Content Delivery Network can help.</strong></p>
<p>A <strong>CDN</strong> can cache and deliver certain types of content from locations closer to users.</p>
<p>AWS CloudFront is an example.</p>
<p>Imagine our application contains images, JavaScript files, CSS files, videos, and other static resources.</p>
<p>Instead of making every user retrieve those files directly from our main infrastructure, a CDN can deliver cached content from locations closer to the users.</p>
<p>This can improve the user experience while also reducing the amount of traffic reaching the origin infrastructure.</p>
<p>That gives us another lesson.</p>
<p>Cloud architecture is also about where content is delivered from, not just where the application is running.</p>
<p> <strong>Where Should User Files Go?</strong></p>
<p>There is another question we need to answer.</p>
<p>What happens when users upload files?</p>
<p>Maybe they upload profile pictures.</p>
<p>Maybe they upload documents.</p>
<p>Maybe they upload videos.</p>
<p>Maybe the application generates reports that need to be stored.</p>
<p>Should we keep all these files on our application server?</p>
<p>It might work at first.</p>
<p>But it creates a problem.</p>
<p>Application servers should ideally be replaceable.</p>
<p>If one server disappears, we should not lose important user data because that data happened to be stored on that particular machine.</p>
<p> <strong>This is where object storage becomes useful.</strong></p>
<p><strong>Amazon</strong> <strong>S3</strong> is an example of an object storage service.</p>
<p>Instead of treating uploaded files as something that belongs permanently to a particular application server, we can store those objects separately.</p>
<p>The application can then access them whenever they are needed.</p>
<p>This separation is important.</p>
<p>Our application servers can come and go.</p>
<p>The data should not have to disappear with them.</p>
<p> <strong>Designing for Failure</strong></p>
<p>One of the biggest changes in my thinking about cloud infrastructure has been understanding that failure is not necessarily an unusual event.</p>
<p>Servers fail.</p>
<p>Applications crash.</p>
<p>Networks have problems.</p>
<p>Databases can become unavailable.</p>
<p>Configurations can be wrong.</p>
<p>Deployments can introduce bugs.</p>
<p>The question is not simply whether something will fail.</p>
<p>The better question is:</p>
<p><strong>What happens when it does?</strong></p>
<p>Suppose we have three application servers and one suddenly crashes.</p>
<p>If our load balancer has health checks configured correctly, it can identify that the server is unhealthy and stop sending traffic to it.</p>
<p>The other servers can continue handling requests.</p>
<p>Meanwhile, another server can be launched to replace the failed one if the environment is configured for it.</p>
<p>The users may never know what happened.</p>
<p>That is the idea behind resilient architecture.</p>
<p>It does not mean failures stop happening.</p>
<p>It means one failure does not necessarily bring down the entire system.</p>
<p> <strong>Monitoring Is Not Optional</strong></p>
<p>There is another part of the architecture that is easy to overlook.</p>
<p>How do we know what is happening inside the environment?</p>
<p>Imagine waking up one morning and finding messages from users saying the application is slow.</p>
<p>You check the application.</p>
<p>It is running.</p>
<p>You check the servers.</p>
<p>They are running.</p>
<p>But something is clearly wrong.</p>
<p>Without proper monitoring and logs, you may spend hours guessing.</p>
<p>With monitoring, you may discover that CPU usage has suddenly increased.</p>
<p>Maybe memory usage is unusually high.</p>
<p>Maybe database connections have reached their limit.</p>
<p>Maybe response times have increased.</p>
<p>Maybe one API endpoint is returning thousands of errors.</p>
<p>Monitoring gives us visibility.</p>
<p>Instead of guessing what is wrong, we can investigate what the infrastructure is actually doing.</p>
<p>Services such as <strong>Amazon</strong> <strong>CloudWatch</strong> can help collect metrics, logs, and other information that can be used to understand the health and performance of our environment.</p>
<p>This becomes even more important as the infrastructure grows.</p>
<p>When you have one server, you might be able to check it manually.</p>
<p>When you have dozens or hundreds of resources, manual inspection is no longer practical.</p>
<p> <strong>So Where Does DevOps Come In?</strong></p>
<p>At this point, someone might ask:</p>
<p>Isn't everything we have discussed just cloud computing?</p>
<p>Yes.</p>
<p>But this is where DevOps connects the different pieces.</p>
<p>Imagine that a developer makes a change to the application.</p>
<p>The code is pushed to GitHub.</p>
<p>A CI pipeline can automatically build the application and run tests.</p>
<p>If the tests pass, the application can move to the next stage.</p>
<p>The application can be packaged into a container.</p>
<p>That container can be stored in a container registry.</p>
<p>Infrastructure can be provisioned using Infrastructure as Code.</p>
<p>The application can then be deployed to the required environment.</p>
<p>After deployment, monitoring can help determine whether the application is behaving as expected.</p>
<p>If something goes wrong, engineers investigate the problem, fix it, and improve the process.</p>
<p>This is where DevOps becomes more than knowing how to use individual tools.</p>
<p>Git is useful.</p>
<p>Docker is useful.</p>
<p>Kubernetes is useful.</p>
<p>Terraform is useful.</p>
<p>AWS is useful.</p>
<p>But knowing how these tools fit into the bigger picture is even more important.</p>
<p>The goal is not to collect tools.</p>
<p>The goal is to build a reliable process for developing, deploying, operating, monitoring, and improving software.</p>
<p><strong>The Architecture Keeps Evolving</strong></p>
<p>Look at where we started.</p>
<p>One server.</p>
<p>One application.</p>
<p>One database.</p>
<p>Then we encountered problems.</p>
<p>We needed more capacity, so we introduced additional <strong>servers</strong>.</p>
<p>We needed a way to distribute traffic, so we introduced <strong>a</strong> <strong>load</strong> <strong>balancer</strong>.</p>
<p>We needed the infrastructure to respond to changing demand, so we introduced <strong>Auto</strong> <strong>Scaling</strong>.</p>
<p>We needed to think about database reliability and performance, so we considered a managed database.</p>
<p>We needed to serve content efficiently to users in different locations, so we introduced a <strong>CDN</strong>.</p>
<p>We needed somewhere reliable to store files, so we considered <strong>object</strong> <strong>storage</strong>.</p>
<p>We needed visibility into the environment, so we introduced <strong>monitoring</strong>.</p>
<p>We needed a reliable way to build and deploy changes, so <strong>DevOps</strong> practices became important.</p>
<p>Notice something.</p>
<p>We did not introduce all these components simply because they were available.</p>
<p>Each one addressed a problem.</p>
<p>That distinction matters.</p>
<p>Good cloud architecture is not about using as many services as possible.</p>
<p>It is about understanding the problem and choosing an appropriate solution.</p>
<p>More Services Do Not Always Mean a Better Architecture</p>
<p>There is a temptation when learning cloud computing to think that a more complicated architecture must be a better architecture.</p>
<p>I do not think that is true.</p>
<p>If an application has a small number of users and simple requirements, it may not need an elaborate architecture.</p>
<p>There may be no reason to introduce Kubernetes simply because Kubernetes is popular.</p>
<p>There may be no reason to build a complicated multi region architecture if the application does not require it.</p>
<p>There may be no reason to introduce ten different services when three can solve the problem properly.</p>
<p>Every additional component introduces complexity.</p>
<p>It needs to be configured.</p>
<p>It needs to be secured.</p>
<p>It needs to be monitored.</p>
<p>It may need to be maintained.</p>
<p>And it may increase the cost of running the application. </p>
<p><strong>The goal is not complexity.</strong></p>
<p>The goal is to build an environment that is reliable, scalable, secure, maintainable, and appropriate for the application's actual requirements.</p>
<p>So, What Happens When 10,000 Users Show Up?</p>
<p>Let's return to the question that started this whole discussion.</p>
<p>What happens when 10,000 users show up?</p>
<p>The answer depends on how we designed the system.</p>
<p>Users send requests to the application.</p>
<p>A load balancer receives the traffic and distributes requests across available application servers.</p>
<p>If demand increases, Auto Scaling can add more compute resources based on the rules we have configured.</p>
<p>The application servers communicate with the database.</p>
<p>Files can be stored separately using object storage.</p>
<p>A CDN can deliver cached content closer to users.</p>
<p>Monitoring helps us understand what is happening across the environment.</p>
<p>Automation helps us deploy changes consistently.</p>
<p>If one application server fails, the remaining servers can continue serving users.</p>
<p>If traffic decreases, unnecessary compute resources can be removed.</p>
<p><strong>None of this happens by magic.</strong></p>
<p>It happens because the architecture was designed with these possibilities in mind.</p>
<p><strong>What I Took Away From This</strong></p>
<p>The biggest lesson for me is that cloud engineering is not really about memorising cloud services.</p>
<p>It is about understanding problems.</p>
<p>A load balancer exists because distributing traffic can be better than sending everything to one server.</p>
<p>Auto Scaling exists because demand changes.</p>
<p>A managed database exists because databases require reliability, performance, backups, and proper management.</p>
<p>A CDN exists because users are not all sitting next to our servers.</p>
<p>Object storage exists because application servers should not necessarily be responsible for storing everything.</p>
<p>Monitoring exists because we cannot manage what we cannot see.</p>
<p>DevOps practices exist because deploying software manually and repeatedly is not a reliable way to operate modern applications at scale.</p>
<p>Once I started looking at cloud architecture this way, the services became easier to understand.</p>
<p>Instead of asking:</p>
<p><strong>“What does this AWS service do?”</strong></p>
<p>I started asking:</p>
<p><strong>“What problem does this solve?”</strong></p>
<p>That small change in perspective makes a huge difference.</p>
<p>Because the goal of cloud engineering is not to build the most complicated architecture possible.</p>
<p>The goal is to build an architecture that makes sense for the problem.</p>
<p>Maybe your application has ten users today.</p>
<p>Maybe it will have 10,000 tomorrow.</p>
<p>You may not need a complicated architecture from day one.</p>
<p>But you should understand what happens when the simple solution eventually stops being enough.</p>
<p>That is the part of Cloud and DevOps Engineering I am beginning to appreciate the most.</p>
<p>It is not just about getting an application to work.</p>
<p>It is about thinking ahead.</p>
<p>Because when 10,000 users eventually show up, you do not want to be asking for the first time:</p>
<p>“What are we going to do now?”</p>
<p>You want your architecture to already have an answer.</p>
]]></content:encoded></item><item><title><![CDATA[Eight Services Came Alive with One Command and I Was Not Ready for What Happened Next]]></title><description><![CDATA[I typed one command into my terminal and watched twelve containers spin up in sequence, connect to each other, register themselves, and serve a fully working application without me touching anything e]]></description><link>https://tekvanguard.hashnode.dev/eight-services-came-alive-with-one-command-and-i-was-not-ready-for-what-happened-next</link><guid isPermaLink="true">https://tekvanguard.hashnode.dev/eight-services-came-alive-with-one-command-and-i-was-not-ready-for-what-happened-next</guid><category><![CDATA[Docker]]></category><category><![CDATA[containers]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Devops articles]]></category><category><![CDATA[deployment]]></category><dc:creator><![CDATA[ijeoma ajah]]></dc:creator><pubDate>Fri, 26 Jun 2026 17:07:38 GMT</pubDate><content:encoded><![CDATA[<p>I typed one command into my terminal and watched twelve containers spin up in sequence, connect to each other, register themselves, and serve a fully working application without me touching anything else.</p>
<p>That was not theory. That was not a tutorial I followed passively. That was my actual deployment of Spring Petclinic microservices as part of the DevOps Micro Internship (DMI) Cohort 2 personal project requirement, and it changed the way I think about distributed systems.</p>
<p>This post documents exactly what I did, how I did it, and the specific lessons that stuck with me after the containers came down.</p>
<p><strong>What Is Spring Petclinic and What Did I Deploy</strong></p>
<p>Spring Petclinic is a sample application built by the Spring team to demonstrate how a real-world microservices architecture looks in practice. It simulates a veterinary clinic management system and breaks the functionality across multiple independent services instead of running everything as one monolith.</p>
<p>The version I deployed is the microservices edition. It runs eight application services:</p>
<ul>
<li><p><strong>Config Server</strong> on port 8888</p>
</li>
<li><p><strong>Discovery Server (Eureka)</strong> on port 8761</p>
</li>
<li><p><strong>API Gateway</strong> on port 8080</p>
</li>
<li><p><strong>Customers Service</strong> on port 8081</p>
</li>
<li><p><strong>Visits Service</strong> on port 8082</p>
</li>
<li><p><strong>Vets Service</strong> on port 8083</p>
</li>
<li><p><strong>GenAI Service</strong> on port 8084</p>
</li>
<li><p><strong>Spring Boot Admin</strong> on port 9090</p>
</li>
</ul>
<p>On top of those eight, the stack includes a full observability layer: <strong>Prometheus</strong> on port 9091, <strong>Grafana</strong> on port 3030, and <strong>Zipkin</strong> on port 9411.</p>
<p>Eleven containers in total, all coordinated by a single <code>docker-compose.yml</code> file. I deployed this on an AWS EC2 instance running Ubuntu because my local machine did not have the memory headroom to run the full stack comfortably. I connected via SSH and ran everything from the command line.</p>
<p><strong>Prerequisites</strong></p>
<p>Before anything runs, two things need to be in place.</p>
<p><strong>Docker</strong> is the only real requirement. I was running Docker version 29.2.1, build a5c7197. You can confirm your version with:</p>
<p><strong>bash</strong></p>
<pre><code class="language-bash">docker --version
</code></pre>
<p><strong>Git</strong> is needed to pull the repository. Any recent version works.</p>
<p>No Java installation, no Maven setup, no IDE required. Docker handles all of that inside the containers. That simplicity is one of the things that makes Docker Compose genuinely powerful for deploying complex applications quickly.</p>
<p><strong>Step One:</strong> Clone the Repository, I forked the official Spring Petclinic microservices repo under my GitHub account and cloned my fork:</p>
<p>bash</p>
<pre><code class="language-plaintext">git clone https://github.com/TekVanguard/spring-petclinic-microservices.git 
</code></pre>
<p>Once the clone finished, I navigated into the directory:</p>
<p>bash</p>
<pre><code class="language-plaintext">cd spring-petclinic-microservices
</code></pre>
<p>Running <code>ls</code> confirmed the expected structure was there:</p>
<p><strong>code</strong></p>
<pre><code class="language-plaintext">CONTRIBUTING.md  docker  mvnw  scripts
spring-petclinic-config-server
spring-petclinic-genai-service
LICENSE  docker-compose.yml  mvnw.cmd
spring-petclinic-admin-server
spring-petclinic-customers-service
spring-petclinic-vets-service
README.md  docs  pom.xml
spring-petclinic-api-gateway
spring-petclinic-discovery-server
spring-petclinic-visits-service
</code></pre>
<p>Every service has its own subdirectory. The docker-compose.yml at the root is what ties it all together.</p>
<p><strong>Step Two:</strong> Start All Services One command brings the entire stack up in detached mode:</p>
<p>bash</p>
<pre><code class="language-plaintext">docker compose up -d
</code></pre>
<p>Docker pulled the images that were not already cached, created the shared network, and started all containers according to the dependency order defined in the Compose file.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a3b2cdac3746f347e362fe5/ad5a5994-cdee-4cb2-985c-92f9ab2433cf.png" alt="" style="display:block;margin:0 auto" />

<p>This is where I first noticed something important about how the stack is designed.</p>
<p><strong>Why Config Server and Discovery Server Start First</strong></p>
<p>When you look at the docker-compose.yml carefully, you will see that most services declare a <strong>depends_on</strong> condition pointing back to <strong>config</strong>-<strong>server</strong> and <strong>discovery</strong>-<strong>server</strong>. This is not cosmetic. It is a hard architectural requirement.</p>
<p><strong>Config Server</strong> acts as the central configuration source for every other service. At startup, each service reaches out to Config Server to pull its own configuration properties. Things like database connection strings, service ports, and feature flags all live there. If Config Server is not ready when another service starts, that service has nothing to read and will either fail to start or enter a crash loop.</p>
<p><strong>Discovery Server</strong> (<strong>Eureka</strong>) is the registry where every service announces its presence. Once a service is up and running, it registers with Eureka. Other services then query Eureka to find where their dependencies are. Without Eureka being healthy first, the services have no mechanism to locate each other, and inter-service calls fail immediately.</p>
<p>The <strong>depends_on</strong> with <strong>condition</strong>: <strong>service_healthy</strong> in the Compose file enforces this. Docker checks the health endpoint of Config Server and Discovery Server before allowing the downstream services to start. You can watch this in real time when you run the stack: config-server and discovery-server reach the Healthy state first, and only then do the other containers begin their startup sequence.</p>
<p>Getting this startup dependency wrong is one of the most common reasons a fresh deployment of this stack fails. The error messages you get from a service that could not reach Config Server at startup are not always obvious about the root cause, which makes understanding the dependency chain worth knowing before you deploy, not after.</p>
<p><strong>Step Three:</strong> Verify Everything Is Running After the containers started, I confirmed all services were up:</p>
<p>bash</p>
<pre><code class="language-plaintext">docker compose ps
</code></pre>
<p>The output showed all containers with status Up. I then verified each service by opening its URL.</p>
<p>The Spring Petclinic application at <a href="http://98.91.190.4:8080">http://98.91.190.4:8080</a> loaded the full frontend.</p>
<p>I navigated to Find Owners, retrieved the pre-loaded list of pet owners, clicked through to individual owners to view their pets and visit history, and added a test visit to confirm write operations were working end to end.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a3b2cdac3746f347e362fe5/cb998006-afbf-4218-a30b-6ceac234897d.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/6a3b2cdac3746f347e362fe5/5a9c85b7-1957-46d5-88e1-a4e806128c8c.png" alt="" style="display:block;margin:0 auto" />

<p>The Eureka dashboard at <a href="http://98.91.190.4:8761">http://98.91.190.4:8761</a> showed five services successfully registered:</p>
<p>Application Status ADMIN-SERVER UP API-GATEWAY UP CUSTOMERS-SERVICE UP VETS-SERVICE UP VISITS-SERVICE UP Spring Boot Admin at <a href="http://98.91.190.4:9090">http://98.91.190.4:9090</a> gave a consolidated view of all registered services with their health indicators, memory usage, and thread counts in a single dashboard.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a3b2cdac3746f347e362fe5/d141b3c3-6520-40fc-a066-61008cafc11a.png" alt="" style="display:block;margin:0 auto" />

<p>The Observability Stack: What Prometheus, Grafana, and Zipkin Actually Showed Me This section is the one I want to spend the most time on because it was the most instructive part of the whole deployment.</p>
<p>Prometheus at <a href="http://98.91.190.4:9091">http://98.91.190.4:9091</a> was already scraping metrics from every service before I ran a single query. I ran the query http_server_requests_seconds_count and got back a full breakdown of every HTTP endpoint across all services, how many times each had been called, and the cumulative time spent processing requests. Prometheus does not wait for you to configure it. The targets were already wired in via the Compose setup. You open it and the data is already there.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a3b2cdac3746f347e362fe5/ef88c429-0e88-454e-a227-939b9bebb832.png" alt="" style="display:block;margin:0 auto" />

<p>Grafana at <a href="http://98.91.190.4:3030">http://98.91.190.4:3030</a> was the layer that turned those raw Prometheus numbers into something readable. Pre-built dashboards were already loaded, showing service latency, request rates, JVM memory consumption, and garbage collection activity. The difference between reading metric names in Prometheus and seeing the same data rendered as time-series graphs in Grafana is significant. Grafana makes it immediately obvious where a service is healthy and where it is under pressure.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a3b2cdac3746f347e362fe5/d37b353d-6774-4802-947d-279e9a04c029.png" alt="" style="display:block;margin:0 auto" />

<p>Zipkin at <a href="http://98.91.190.4:9411">http://98.91.190.4:9411</a> was the one that actually made me stop and think. Zipkin does distributed tracing, which means it stitches together all the individual hops a single user request makes across multiple services into one coherent trace. When I searched for recent traces in Zipkin, I could see a request coming in at the API Gateway, being forwarded to Customers Service, and each hop labeled with the exact time it took. You could see which service was the slowest, which calls were sequential versus parallel, and where the total latency was accumulating.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a3b2cdac3746f347e362fe5/351ba556-bee5-47be-b203-8e907e7aa7e1.png" alt="" style="display:block;margin:0 auto" />

<p>Running a microservices stack without distributed tracing is like debugging a relay race where you can only see the finish line. Zipkin shows you every baton handoff.</p>
<p>Shutting Everything Down When I was done, I ran:</p>
<p>bash</p>
<pre><code class="language-plaintext">docker compose down
</code></pre>
<p>The output confirmed all twelve containers were stopped and removed cleanly, and the network was taken down:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a3b2cdac3746f347e362fe5/ef171439-6749-4b80-94db-d85c8614d112.png" alt="" style="display:block;margin:0 auto" />

<p>docker compose up -d creates the environment. docker compose down destroys it. That cycle, reproducible and clean every single time, is the core promise of containerized deployments.</p>
<p>The Key Lesson: Observability Is Not Optional. Before this deployment, observability felt like a bonus feature. Something you added later when a system got complex enough to need it. After running this stack and seeing Prometheus, Grafana, and Zipkin working together out of the box, I understand it differently now.</p>
<p>Observability is not something you bolt on after the fact. It is part of what makes a distributed system understandable. When you have eight services passing requests between each other, you cannot reason about the system by looking at individual service logs in isolation. You need a layer that treats the whole system as the unit of observation. Prometheus gives you the numbers. Grafana gives you the trend. Zipkin gives you the path.</p>
<p>Deploying this stack and running it yourself, even locally, teaches you things that no amount of reading can replicate.</p>
<p><strong>This Is Part of DMI</strong></p>
<p>This deployment was completed as part of the DevOps Micro Internship (DMI) Cohort 2, a hands-on programme built around real DevOps workflows, real tools, and real team delivery under mentorship of Pravin Mishra and the CloudAdvisory team.</p>
<p>If you want this kind of hands-on experience for yourself, DMI Cohort 3 starts 27 June 2026. Applications are open now. 👉 Register here: <a href="https://docs.google.com/forms/d/e/1FAIpQLSel7ai7nyb0P1qLW4vEyfB_nEsD4lUF1XG88vmAaFGBOb6hPA/viewform">https://docs.google.com/forms/d/e/1FAIpQLSel7ai7nyb0P1qLW4vEyfB_nEsD4lUF1XG88vmAaFGBOb6hPA/viewform</a></p>
<p>Join the DMI--DevOps Micro Internship Community here: <a href="https://discord.pravinmishra.com/">https://discord.pravinmishra.com/</a></p>
<p>Have you deployed a microservices stack before? What part of the deployment and observability layer surprised you the most when you first saw it working? Drop it in the comments.</p>
]]></content:encoded></item><item><title><![CDATA[We Deployed a Production Grade App on AWS as a Team of 11 and Here Is Everything That Actually Happened
]]></title><description><![CDATA[Nobody tells you that the hardest part of a cloud deployment is not the code. It is eleven engineers, one shared repository, and a single Kubernetes cluster that does not care about your feelings.
Tha]]></description><link>https://tekvanguard.hashnode.dev/we-deployed-a-production-grade-app-on-aws-as-a-team-of-11-and-here-is-everything-that-actually-happened</link><guid isPermaLink="true">https://tekvanguard.hashnode.dev/we-deployed-a-production-grade-app-on-aws-as-a-team-of-11-and-here-is-everything-that-actually-happened</guid><category><![CDATA[AWS]]></category><category><![CDATA[EKS]]></category><category><![CDATA[rds]]></category><category><![CDATA[deployment]]></category><category><![CDATA[Microservices]]></category><dc:creator><![CDATA[ijeoma ajah]]></dc:creator><pubDate>Thu, 25 Jun 2026 05:33:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a3b2cdac3746f347e362fe5/0844d56d-d661-4367-825a-74a3ddf7a90d.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Nobody tells you that the hardest part of a cloud deployment is not the code. It is eleven engineers, one shared repository, and a single Kubernetes cluster that does not care about your feelings.</p>
<p>That is exactly what my team and I walked into during our DMI Cohort 2 final project. And by the time it was over, we had built something I am genuinely proud of.</p>
<p><strong>What We Built and Why</strong></p>
<p>We took the Spring PetClinic application, an open source veterinary management system, and turned it into a fully cloud native deployment running on AWS. The goal was never just to get the app online. The goal was to build a real world DevOps pipeline from the ground up. That meant infrastructure as code, containerization, Kubernetes orchestration, automated CI/CD, GitOps delivery, and full observability.</p>
<p>The Spring PetClinic platform manages pet owners, veterinarians, pets, and visit records through multiple independently running microservices. We deployed the entire thing on Amazon EKS in the AWS eu north 1 region. We provisioned the infrastructure with Terraform, containerized every service with Docker, stored images in Amazon ECR, connected a managed database through Amazon RDS, and built a CI/CD pipeline using GitHub Actions.</p>
<p>This was not a tutorial project. It was a full production grade setup handled by eleven engineers working across every layer of the stack.</p>
<p><strong>My Role: GitOps and Continuous Delivery with ArgoCD</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/6a3b2cdac3746f347e362fe5/9acb9b04-c440-47e7-95b0-daa16684cdb5.png" alt="" style="display:block;margin:0 auto" />

<p>My specific assignment was the GitOps layer using ArgoCD. This is where deployment stops being a manual process and becomes an automated, self healing workflow.</p>
<p><strong>Here is what I actually did.</strong></p>
<p>I installed ArgoCD inside the Amazon EKS cluster in a dedicated namespace. After installation, I configured the ArgoCD server and set up access to the user interface. The next step was connecting our private infrastructure repository to ArgoCD using a GitHub Personal Access Token. This gave ArgoCD the ability to watch the repo continuously and detect whenever Kubernetes manifests changed.</p>
<p>I then wrote the ArgoCD Application resource manifest using an application.yaml file. This file defined the GitHub repository source, the path to our Kubernetes manifests, the deployment namespace, and the automatic synchronization behavior. Once this was applied to the cluster, ArgoCD took over.</p>
<p>The technical decision I am most satisfied with was choosing automated sync over manual sync. Some teams prefer to review changes before ArgoCD applies them. In this project, I configured ours to sync automatically because we already had GitHub Actions handling the CI side. The pipeline would test, build, and push the updated image. ArgoCD would detect the manifest change and reconcile the cluster to match Git. No human needed to run kubectl apply. No room for human error in the deployment step.</p>
<p>When we validated this in practice, watching the ArgoCD dashboard transition an application from OutOfSync to Synced after a repository push was genuinely satisfying. That visual confirmation meant our GitOps workflow was live.</p>
<p><strong>The Full Architecture</strong></p>
<p>The stack our team built together looks like this.</p>
<p><strong>Cloud Platform:</strong> AWS EKS for Kubernetes orchestration, Amazon ECR for container image storage, Amazon RDS for the managed relational database.</p>
<p><strong>Containerization:</strong> Docker and Docker Compose for local development and image packaging.</p>
<p><strong>Infrastructure as Code:</strong> Terraform for provisioning every AWS resource in a repeatable and version controlled way.</p>
<p><strong>CI/CD:</strong> GitHub Actions for automated build, test, and image push workflows.</p>
<p><strong>GitOps:</strong> ArgoCD for continuous delivery and Kubernetes state synchronization.</p>
<p><strong>Observability:</strong> Prometheus and Grafana for metrics and dashboards, Zipkin for distributed tracing.</p>
<p><strong>Security:</strong> AWS Secrets Manager integrated through the External Secrets Operator and IAM Roles for Service Accounts to keep sensitive credentials out of Kubernetes manifests entirely.</p>
<p><strong>What Went Well and What I Would Do Differently</strong></p>
<p><strong>What worked:</strong> The separation of concerns across the pipeline. GitHub Actions owned the CI side. ArgoCD owned the CD side. The two systems handed off cleanly without overlapping responsibilities.</p>
<p><strong>What I would do differently:</strong> I would implement the ArgoCD App of Apps pattern from the start. We deployed individual ArgoCD applications for each microservice, which worked but created overhead when changes needed to be made across the board. App of Apps would have given us a single parent application to manage the entire platform hierarchy.</p>
<p>I would also invest more time in ArgoCD notifications early on. Having automated alerts when a sync fails or an application drifts from desired state would have saved us debugging time during the final sprint.</p>
<p><strong>This Is Part of DMI</strong></p>
<p>This project was built as part of the DevOps Micro-Internship by Pravin Mishra, a structured program that gives engineers hands-on experience with real production workflows. If you are looking to build practical DevOps skills in a collaborative environment, Join the discord community, do not miss it: <a href="https://discord.pravinmishra.com/">https://discord.pravinmishra.com/</a></p>
<p>Eleven engineers. One cluster. Zero excuses.</p>
<p>What part of this stack would you want to deep dive into next? Do share with me.</p>
]]></content:encoded></item></channel></rss>