<?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" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ PaaS - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ PaaS - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sat, 15 Aug 2026 13:14:46 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/paas/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Diagnose Production Bugs When You Can't Reproduce Them Locally ]]>
                </title>
                <description>
                    <![CDATA[ Every developer eventually encounters the same frustrating problem. A customer reports that your application is failing in production. You try the exact same workflow on your development machine, but  ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-diagnose-production-bugs-when-you-can-t-reproduce-them-locally/</link>
                <guid isPermaLink="false">6a63d10c86ddd43ae5c0036e</guid>
                
                    <category>
                        <![CDATA[ debugging ]]>
                    </category>
                
                    <category>
                        <![CDATA[ production ]]>
                    </category>
                
                    <category>
                        <![CDATA[ PaaS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Environment ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Software Engineering ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Manish Shivanandhan ]]>
                </dc:creator>
                <pubDate>Fri, 24 Jul 2026 20:54:36 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/950ab466-32a5-43f5-a9d6-2146b145f0dc.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Every developer eventually encounters the same frustrating problem.</p>
<p>A customer reports that your application is failing in production. You try the exact same workflow on your development machine, but everything works perfectly. Your teammates can't reproduce the issue either. Automated tests pass. There are no obvious code changes that explain the failure.</p>
<p>Meanwhile, customers continue to experience the bug.</p>
<p>These issues are among the most difficult to solve because the problem often isn't the code itself. It's the environment the code is running in. Differences in configuration, infrastructure, traffic patterns, operating systems, dependencies, or production data can expose bugs that never appear during development.</p>
<p>Here's the uncomfortable truth: most of that difficulty is self-inflicted. Every server you manage, every log pipeline you wire together, and every configuration file you maintain by hand adds to an invisible infrastructure tax. And you pay that tax at the worst possible moment, when production is down and customers are waiting.</p>
<p>Fortunately, production-only bugs can be investigated systematically. In this article, you'll learn how to approach these issues using logs, metrics, distributed tracing, and environment analysis. You'll also see why applications running on a Platform as a Service (PaaS) are significantly easier to debug when things go wrong, because someone else is paying the tax for you.</p>
<h3 id="heading-what-well-cover">What We'll Cover:</h3>
<ul>
<li><p><a href="#heading-why-does-production-behave-differently">Why Does Production Behave Differently?</a></p>
</li>
<li><p><a href="#heading-start-with-evidence-not-assumptions">Start with Evidence, Not Assumptions</a></p>
</li>
<li><p><a href="#heading-logs-tell-you-what-happened">Logs Tell You What Happened</a></p>
</li>
<li><p><a href="#heading-metrics-reveal-trends">Metrics Reveal Trends</a></p>
</li>
<li><p><a href="#heading-distributed-tracing-connects-every-service">Distributed Tracing Connects Every Service</a></p>
</li>
<li><p><a href="#heading-reproduce-production-as-closely-as-possible">Reproduce Production as Closely as Possible</a></p>
</li>
<li><p><a href="#heading-isolate-environmental-variables">Isolate Environmental Variables</a></p>
</li>
<li><p><a href="#heading-a-simple-production-only-bug">A Simple Production-only Bug</a></p>
</li>
<li><p><a href="#heading-verify-the-deployment-itself">Verify the Deployment Itself</a></p>
</li>
<li><p><a href="#heading-do-you-actually-need-a-paas">Do You Actually Need a PaaS?</a></p>
</li>
<li><p><a href="#heading-why-debugging-is-easier-on-a-paas">Why Debugging is Easier on a PaaS</a></p>
</li>
<li><p><a href="#heading-build-applications-that-are-easy-to-debug">Build Applications That Are Easy to Debug</a></p>
</li>
</ul>
<h2 id="heading-why-does-production-behave-differently">Why Does Production Behave Differently?</h2>
<p>Many developers think of production as simply a larger version of their local machine.</p>
<p>In reality, production environments are often very different.</p>
<p>A production application may run across multiple servers or containers behind a <a href="https://www.cloudflare.com/learning/performance/what-is-load-balancing/">load balancer</a>. It may connect to databases containing millions of records, communicate with third-party APIs, use distributed caches, process background jobs, and serve thousands of concurrent users.</p>
<p>Even seemingly small differences can introduce unexpected failures.</p>
<p>Imagine testing an API locally using simple English names like "John Smith." Everything works perfectly. In production, a customer submits a name containing emojis or accented characters, triggering an encoding issue that was never covered by your tests.</p>
<p>Or perhaps your application assumes an environment variable always exists because it's configured on every developer machine. During deployment, that variable is accidentally omitted, causing production requests to fail.</p>
<p>The code hasn't changed. The environment has.</p>
<p>Notice what these failures have in common. None of them are business logic problems. They're environment problems, and every piece of infrastructure your team owns and configures by hand is another surface where your environment can silently drift away from what your code expects. The more infrastructure you manage yourself, the more of these surfaces exist.</p>
<p>Understanding that production behaves differently is the first step toward diagnosing these issues.</p>
<h2 id="heading-start-with-evidence-not-assumptions">Start with Evidence, Not Assumptions</h2>
<p>When production starts failing, it's tempting to immediately start editing code.</p>
<p>Resist that temptation.</p>
<p>The fastest way to solve complex bugs is to gather evidence before making changes.</p>
<p>Start by answering questions such as:</p>
<ul>
<li><p>When did the issue begin?</p>
</li>
<li><p>Did it appear immediately after a deployment?</p>
</li>
<li><p>Does it affect every customer or only a small group?</p>
</li>
<li><p>Is every application instance failing?</p>
</li>
<li><p>Did infrastructure metrics change around the same time?</p>
</li>
</ul>
<p>Every answer narrows the search space.</p>
<p>But here's what nobody tells you: how quickly you can answer these questions depends almost entirely on your infrastructure, not your debugging skills.</p>
<p>If deployment history lives in one system, logs in another, and metrics in a third, answering even the first question means logging into three tools and manually lining up timestamps. The investigation can stall before it starts, not because the bug is hard, but because your tooling is fragmented.</p>
<p>Instead of guessing what might be wrong, you're building a timeline of events that points toward the root cause.</p>
<p>Good debugging is an investigation, not an experiment. And an investigation is only as fast as your access to the evidence.</p>
<h2 id="heading-logs-tell-you-what-happened">Logs Tell You What Happened</h2>
<p>Application logs are usually the first source of information during an incident.</p>
<p>Unfortunately, many applications generate logs that provide almost no useful context.</p>
<p>A message like this offers very little value:</p>
<pre><code class="language-plaintext">Error processing request.
</code></pre>
<p>Compare that with this example:</p>
<pre><code class="language-plaintext">Timestamp: 2026-07-13T09:41:17Z
RequestId: 91df72
CustomerId: 48291
Endpoint: POST /orders
Database: OrdersDB
Duration: 3.2 seconds
Exception: TimeoutException
</code></pre>
<p>Now you know when the failure occurred, which customer experienced it, which endpoint was affected, how long the request took, and what exception caused it.</p>
<p>So where does each of these logs come from? The first one is what you get by default. It's the product of a hurried <code>catch</code> block written while the developer was focused on the happy path, something like this:</p>
<pre><code class="language-csharp">catch (Exception)
{
    logger.LogError("Error processing request.");
}
</code></pre>
<p>The exception is caught, but everything useful about it, including the exception itself, is thrown away. The log message records <em>that</em> something failed, but nothing about <em>what</em>, <em>where</em>, or <em>for whom</em>.</p>
<p>The second log doesn't come from a fancier tool. It comes from a developer deciding, at the moment they wrote the code, what a future 3 a.m. investigator would need to know.</p>
<p>In practice, useful logs come from a few deliberate habits:</p>
<ul>
<li><p><strong>Always log the exception object itself</strong>, not just a message, so the type and stack trace are preserved.</p>
</li>
<li><p><strong>Attach request context automatically.</strong> Most web frameworks let you enrich every log entry with values like a request ID or customer ID once, in middleware, instead of repeating them in every log statement. In ASP.NET Core, for example, logging scopes do exactly this.</p>
</li>
<li><p><strong>Record what the code was doing</strong>, like the endpoint, the downstream dependency being called, and how long it took, because those are the first questions an investigator asks.</p>
</li>
</ul>
<p>Here's what that looks like in code:</p>
<pre><code class="language-csharp">catch (TimeoutException ex)
{
    logger.LogError(ex,
        "Order creation failed for {CustomerId} on {Endpoint} after {Duration}s",
        customerId, "POST /orders", stopwatch.Elapsed.TotalSeconds);
}
</code></pre>
<p>A good rule of thumb: write every log message for the person debugging an outage six months from now, who has never seen this code. That person is often you.</p>
<p>Notice that the message above uses named placeholders like <code>{CustomerId}</code> instead of string interpolation. That's <strong>structured logging</strong>: instead of flattening everything into one plain-text sentence, each value is stored as a separate named field alongside the message, typically as JSON. The entry above might be stored as:</p>
<pre><code class="language-json">{
  "message": "Order creation failed for 48291 on POST /orders after 3.2s",
  "CustomerId": 48291,
  "Endpoint": "POST /orders",
  "Duration": 3.2,
  "Exception": "TimeoutException"
}
</code></pre>
<p>The payoff is searchability. With plain-text logs, finding every failure for one customer means fuzzy text matching and luck. With structured logs, your monitoring system can run a precise query like <code>CustomerId = 48291 AND Exception = TimeoutException</code> and filter millions of entries in seconds. Libraries like Serilog, or the built-in <code>ILogger</code> in .NET, support this out of the box.</p>
<p>The goal isn't simply to record errors. The goal is to provide enough context that someone investigating the issue can immediately begin asking the right questions.</p>
<p>There's a catch, though. Great logs are worthless if you can't find them.</p>
<p>In self-managed setups, logs are scattered across servers, and teams end up building and babysitting their own aggregation pipelines just to make logs searchable. That's engineering time spent on plumbing, not on the product.</p>
<p>If your team maintains its own log shipping infrastructure, it's worth asking: why are we still doing this ourselves?</p>
<h2 id="heading-metrics-reveal-trends">Metrics Reveal Trends</h2>
<p>Logs explain individual events while metrics explain overall system behavior.</p>
<p>Suppose users report that your application becomes slow every afternoon. Reading thousands of log entries may not reveal anything unusual.</p>
<p>A metrics dashboard, however, might immediately show that CPU usage spikes above 90%, memory consumption steadily increases throughout the day, database latency doubles after lunch, and HTTP error rates climb sharply during peak traffic.</p>
<p>Let's make that concrete with the most common open-source setup: <a href="https://prometheus.io/">Prometheus</a> for collecting metrics and <a href="https://grafana.com/">Grafana</a> for visualizing them.</p>
<p>The workflow has three parts. First, your application exposes its metrics. Most frameworks have a library for this. In ASP.NET Core, adding the <code>prometheus-net</code> package and one line of configuration publishes a <code>/metrics</code> endpoint that reports counters like request totals, response durations, and error counts.</p>
<p>Second, a Prometheus server scrapes that endpoint every few seconds and stores the values as time series.</p>
<p>Third, Grafana turns those time series into dashboards.</p>
<p>Once that's in place, investigating the "slow every afternoon" report stops being guesswork. You open Grafana, set the time range to the last three days, and run a query like this against Prometheus:</p>
<pre><code class="language-plaintext">rate(http_request_duration_seconds_sum[5m])
/ rate(http_request_duration_seconds_count[5m])
</code></pre>
<p>That expression plots your average request duration over time. If the graph shows latency climbing every day between 1 p.m. and 4 p.m., you've confirmed the pattern in about a minute. Adding a second panel that plots CPU usage or database connection counts on the same time axis tells you which resource degrades first, and that's your suspect.</p>
<p>Those observations immediately narrow your investigation. Instead of wondering where to start, you now know exactly when the problem begins and which component is under stress.</p>
<p>Metrics transform isolated failures into recognisable patterns.</p>
<p>But that dashboard doesn't build itself. Someone has to install the agents, configure the exporters, size the time-series database, and keep the whole monitoring stack alive.</p>
<p>In many teams, the monitoring system itself becomes another production system that fails and needs debugging. Monitoring your monitoring is the infrastructure tax at its most absurd, and it's a strong signal that your team is carrying operational weight it never needed to.</p>
<h2 id="heading-distributed-tracing-connects-every-service">Distributed Tracing Connects Every Service</h2>
<p>Modern applications rarely consist of a single application talking to a single database.</p>
<p>A customer request may travel through an API gateway, authentication service, order service, payment processor, inventory system, cache, message queue, and database before returning a response.</p>
<p>When something fails, which service caused the delay?</p>
<p>Distributed tracing answers that question. A trace records the complete lifecycle of an individual request as it moves through your architecture. Each unit of work within the trace, like one service call or one database query, is called a <strong>span</strong>, and every span records when it started and how long it took.</p>
<p>Here's what a real trace looks like when viewed in a tool like <a href="https://www.jaegertracing.io/">Jaeger</a> or Zipkin. A customer reports that checkout is timing out, you look up the trace for their request ID, and you see a waterfall like this:</p>
<pre><code class="language-plaintext">Trace 8f3ac21 — POST /checkout — total: 4.61s

api-gateway            ████                                    45ms
  auth-service         ██                                      38ms
  order-service        ████████████████████████████████████  4.51s
    inventory-db query ██████████████████████████████████    4.29s  ⚠
    payment-api        ███                                    210ms
  response             █                                       12ms
</code></pre>
<p>Reading it takes seconds. The request spent 4.29 of its 4.61 seconds inside a single inventory database query. The gateway, auth service, and payment API are all healthy. Nobody needs to investigate them, and nobody needs to guess.</p>
<p>Under the hood, this works because the first service assigns the request a unique trace ID and passes it along in a header with every downstream call. Each service records its spans against that same ID, so the tracing backend can stitch the full journey back together.</p>
<p>The open standard for all of this is <a href="https://opentelemetry.io/">OpenTelemetry</a>, which has instrumentation libraries for every major language, and in many frameworks enabling it is a few lines of setup rather than manual code changes.</p>
<p>Without tracing, engineers often investigate the wrong service for hours. With tracing, the slowest or failing component is usually visible within seconds.</p>
<p>The problem is that rolling out tracing yourself is a project, not a checkbox. Instrumenting every service, deploying collectors, and storing trace data all take real engineering effort, which is why so many teams that know they need tracing still don't have it.</p>
<p>When observability is something you assemble rather than something your platform provides, it tends to remain permanently on the roadmap while incidents keep arriving on schedule.</p>
<h2 id="heading-reproduce-production-as-closely-as-possible">Reproduce Production as Closely as Possible</h2>
<p>Sometimes logs and traces aren't enough. Eventually you'll need to recreate the production environment.</p>
<p>That doesn't necessarily mean copying your production database onto your laptop. Instead, you'll want to identify the differences between environments.</p>
<ul>
<li><p>Is production running Linux while developers use Windows or macOS?</p>
</li>
<li><p>Does production use Redis while development does not?</p>
</li>
<li><p>Are different runtime versions installed?</p>
</li>
<li><p>Are requests routed through a <a href="https://www.fortinet.com/resources/cyberglossary/reverse-proxy">reverse proxy</a>?</p>
</li>
<li><p>Does the production process handle significantly larger datasets?</p>
</li>
<li><p>Does production receive hundreds of concurrent requests while development receives only one?</p>
</li>
</ul>
<p>Each difference becomes a potential explanation for the bug.</p>
<p>How do you actually close those gaps? A few techniques cover most of them:</p>
<h3 id="heading-containerize-the-application">Containerize the Application</h3>
<p>If production runs your app in a container, run the <em>same image</em> locally and in staging. This single step eliminates operating system, runtime version, and dependency differences at once, because the container carries its environment with it.</p>
<h3 id="heading-define-infrastructure-and-configuration-as-code">Define Infrastructure and Configuration as Code</h3>
<p>Services like Redis, the reverse proxy, and their settings should come from checked-in configuration (a <code>docker-compose.yml</code>, Kubernetes manifests, or Terraform) rather than manual setup.</p>
<p>When staging and production are generated from the same files, they can't quietly disagree. When you need to know whether production sits behind a reverse proxy or what runtime it uses, you read it from the config instead of asking whoever set up the server.</p>
<h3 id="heading-make-the-data-realistic">Make the Data Realistic</h3>
<p>You rarely need real production data, and for privacy reasons you usually shouldn't use it. What you need is data with production's <em>shape</em>: similar volume, and similar messiness.</p>
<p>Seed staging with a few million generated rows, and include the awkward cases, like names with accents and emojis, null-heavy records, and very long strings.</p>
<h3 id="heading-simulate-production-traffic">Simulate Production Traffic</h3>
<p>A bug that only appears under a hundred concurrent requests will never show up when you test one request at a time. Load-testing tools like <a href="https://k6.io/">k6</a> or JMeter let you replay realistic concurrency against staging with a short script, which is often what finally reproduces race conditions and connection pool exhaustion.</p>
<p>The closer your staging environment resembles production, the more likely you are to reproduce production-only failures before customers encounter them.</p>
<p>Notice, again, where the effort goes. Keeping staging faithful to production is a permanent maintenance job when both environments are hand-built, because hand-built environments drift the moment someone applies a patch to one and forgets the other.</p>
<p>Teams that get environment parity for free, because every environment is generated from the same configuration, simply have fewer production-only bugs to chase in the first place.</p>
<h2 id="heading-isolate-environmental-variables">Isolate Environmental Variables</h2>
<p>One of the most effective debugging techniques is changing only one variable at a time.</p>
<p>Imagine your application fails only in production. Potential differences include operating system versions, database engines, container configuration, environment variables, memory limits, network latency, or infrastructure settings.</p>
<p>Instead of modifying several variables simultaneously, test each one individually.</p>
<p>Here's what that looks like in practice. Suppose an API endpoint crashes in production but works locally, and you've identified three differences: production runs PostgreSQL 16 while you develop against 15, production caps the container at 512 MB of memory, and production sets <code>ENVIRONMENT=production</code>.</p>
<p>Don't change all three at once. Test them one at a time, keeping everything else identical:</p>
<pre><code class="language-bash"># Test 1: only the database version changes
docker run -d -p 5432:5432 postgres:16
# → run the failing request. Still works? Postgres is cleared. Revert to 15.

# Test 2: only the memory limit changes
docker run --memory=512m my-app
# → run the failing request. Crashes with an OutOfMemoryError? Found it.
</code></pre>
<p>If the bug appears in test 2 and only test 2, you've found your cause, and just as importantly, you've <em>cleared</em> the other suspects. Had you changed the database version and the memory limit together and seen the crash, you'd still have to untangle which one was responsible.</p>
<p>This disciplined approach often identifies the real cause much faster than random experimentation.</p>
<p>It's also worth pausing on that list of variables. Almost every item on it exists only because your team owns the infrastructure underneath the application. The fewer knobs you personally manage, the fewer variables you'll ever need to isolate.</p>
<h2 id="heading-a-simple-production-only-bug">A Simple Production-only Bug</h2>
<p>Consider this ASP.NET Core endpoint:</p>
<pre><code class="language-csharp">app.MapGet("/discount", () =&gt;
{
    string region = Environment.GetEnvironmentVariable("REGION");

    if (region.ToLower() == "eu")
        return Results.Ok("20% discount");

    return Results.Ok("10% discount");
});
</code></pre>
<p>Everything works perfectly during development.</p>
<p>Then customers begin reporting HTTP 500 errors in production.</p>
<p>Eventually the logs reveal this exception:</p>
<pre><code class="language-plaintext">NullReferenceException
</code></pre>
<p>The issue isn't difficult once you know where to look.</p>
<p>The production deployment forgot to define the <code>REGION</code> environment variable. Calling <code>ToLower()</code> on a null value immediately crashes the request.</p>
<p>The fix is straightforward:</p>
<pre><code class="language-csharp">string region = Environment.GetEnvironmentVariable("REGION") ?? "US";

if (region.Equals("EU", StringComparison.OrdinalIgnoreCase))
    return Results.Ok("20% discount");
</code></pre>
<p>The lesson isn't just about null checking. It's about understanding that production-only bugs are frequently caused by configuration differences rather than faulty business logic.</p>
<p>Without useful logs, developers might spend hours reviewing application code while completely overlooking the deployment configuration.</p>
<p>And step back one level further: this entire class of bug exists because a human had to remember to set a variable on a machine. Configuration drift isn't a coding failure, it's an operational failure, and it's the direct product of managing deployment configuration by hand.</p>
<p>When you find yourself writing runbooks to remind people which variables to set on which servers, that's another "why are we still doing this ourselves?" moment worth taking seriously.</p>
<h2 id="heading-verify-the-deployment-itself">Verify the Deployment Itself</h2>
<p>Not every production issue originates from your source code.</p>
<p>Deployment problems are surprisingly common.</p>
<ul>
<li><p>A container image may not have been updated.</p>
</li>
<li><p>A configuration file might be missing.</p>
</li>
<li><p>A database migration may have failed.</p>
</li>
<li><p>An environment variable could contain an incorrect value.</p>
</li>
<li><p>A required secret may not have been deployed.</p>
</li>
<li><p>A rollback might have restored an older application version without anyone noticing.</p>
</li>
</ul>
<p>Before assuming your code contains a bug, confirm that production is actually running the version you intended to deploy.</p>
<p>Many incidents have been resolved simply by discovering that the wrong build was running.</p>
<p>Every single one of those production failures is a failure of infrastructure process, not of programming. They happen in homegrown deployment pipelines because homegrown pipelines have exactly as much verification as someone found time to build.</p>
<p>If your team can't answer "what version is running right now?" in one glance, your deployment system is generating bugs for you to debug later.</p>
<h2 id="heading-do-you-actually-need-a-paas">Do You Actually Need a PaaS?</h2>
<p>Before we look at how a PaaS changes debugging, an honest question deserves an honest answer: does every team need one?</p>
<p>No. A PaaS is a trade. You hand over infrastructure control and pay a platform premium, and in exchange you stop spending engineering time on servers, pipelines, and observability plumbing. Whether that trade is worth it depends on your situation, and there are legitimate reasons to stay off a platform:</p>
<ul>
<li><p><strong>You have unusual infrastructure requirements:</strong> GPU workloads, custom kernels, exotic networking, or software that needs specific hardware may simply not fit a platform's constraints.</p>
</li>
<li><p><strong>Compliance or data residency rules demand full control:</strong> Some regulated industries need to dictate exactly where and how everything runs.</p>
</li>
<li><p><strong>You operate at a scale where the economics flip:</strong> For very large workloads, the per-resource premium of a PaaS can exceed the cost of a dedicated platform team. That's why companies at massive scale build internal platforms, though note what they build: essentially their own PaaS.</p>
</li>
<li><p><strong>Infrastructure <em>is</em> your product:</strong> If you sell hosting, networking, or infrastructure tooling, operating it yourself is the business.</p>
</li>
</ul>
<p>For everyone else, the evaluation comes down to a few questions worth asking:</p>
<ul>
<li><p>When production breaks, how much of the first hour goes to <em>finding</em> information versus <em>acting</em> on it?</p>
</li>
<li><p>Is anyone on the team maintaining log pipelines, monitoring stacks, or deployment scripts as a side job on top of the product work they were hired for?</p>
</li>
<li><p>Can you say, in one glance, exactly what version is running in production right now?</p>
</li>
<li><p>When did you last lose a day to environment drift, like a bug caused by a server, config, or variable that didn't match?</p>
</li>
</ul>
<p>If those answers make you wince, and for most small-to-medium product teams they do, you're paying the infrastructure tax without getting anything for it. The signal isn't your company's size, but where your engineering hours are going. A two-person startup and a fifty-person product team both come out ahead when nobody is babysitting servers.</p>
<p>And if you're currently unsure whether you need one, you probably do. Teams with a real reason to run their own infrastructure tend to know exactly what that reason is.</p>
<h2 id="heading-why-debugging-is-easier-on-a-paas">Why Debugging is Easier on a PaaS</h2>
<p>The hardest part of diagnosing production bugs often isn't finding the root cause, it's finding the information you need to investigate.</p>
<p>In a traditional infrastructure setup, logs are scattered across multiple virtual machines, containers, load balancers, and background workers. When an application scales horizontally, a single customer request may touch several servers before it completes. Developers often spend more time SSHing into machines, locating log files, and correlating timestamps than actually debugging the problem.</p>
<p>That time is the infrastructure tax coming due. Every hour spent assembling evidence during an incident is an hour of downtime your team chose, months earlier, when it decided to own and operate all of that machinery itself.</p>
<p>A <a href="https://www.freecodecamp.org/news/my-team-s-experience-moving-from-aws-to-a-paas/">Platform as a Service (PaaS)</a> changes that experience completely.</p>
<p>Instead of treating each server as an individual machine to manage, a PaaS treats your application as a single service. Logs from every instance are automatically aggregated into one place, metrics are collected continuously, and health checks are built into the platform. Whether your application is running on one container or fifty, you view it through a single dashboard instead of dozens of terminals.</p>
<p>When a production issue occurs, you can immediately answer important questions.</p>
<ul>
<li><p>Did the problem begin after the latest deployment?</p>
</li>
<li><p>Is every application instance failing or only one?</p>
</li>
<li><p>Did CPU or memory usage spike before the application crashed?</p>
</li>
<li><p>Which release introduced the regression?</p>
</li>
</ul>
<p>Instead of collecting this information manually, the platform already has it available.</p>
<p>Many PaaS tools also maintain deployment history, making it easy to compare application behavior before and after each release. If error rates suddenly increase after version 2.8.1 is deployed, the relationship becomes obvious. Rolling back to a previous deployment often takes only a few minutes, dramatically reducing downtime.</p>
<p>Infrastructure consistency is another major advantage.</p>
<p>Applications deployed through a PaaS are created from the same deployment configuration every time. Developers don't have to wonder whether one server has an outdated runtime, a missing dependency, an incorrect operating system package, or a forgotten environment variable. Consistent environments eliminate an entire category of production-only bugs before they happen.</p>
<p>Remember the <code>REGION</code> bug from earlier? On a platform where configuration is declared once and applied everywhere, that bug never ships.</p>
<p>Perhaps the biggest benefit is faster incident response.</p>
<p>During an outage, engineering teams shouldn't waste valuable time gathering evidence from multiple systems. Centralized logging, built-in monitoring, distributed tracing, deployment history, and health checks allow them to begin investigating immediately.</p>
<p>That translates directly into a lower Mean Time to Resolution (MTTR), shorter outages, and a better experience for both developers and customers.</p>
<h2 id="heading-build-applications-that-are-easy-to-debug">Build Applications That Are Easy to Debug</h2>
<p>Production bugs are inevitable. Complex systems fail in unexpected ways, no matter how experienced the engineering team is.</p>
<p>The difference between mature engineering organizations and everyone else isn't whether bugs occur. It's how quickly they can understand and resolve them.</p>
<p>Write meaningful logs that provide context instead of generic error messages. Collect metrics continuously so performance trends are visible before users complain. Instrument your applications with distributed tracing so requests can be followed across services. Keep staging environments as close to production as possible, and treat infrastructure configuration as carefully as application code.</p>
<p>Just as importantly, choose a platform that makes debugging easier instead of harder.</p>
<p>Teams relying on manually managed servers often spend the first hour of an incident simply gathering logs and connecting to machines. Teams running on a modern PaaS begin with the evidence already in front of them. They can correlate deployments with error spikes, inspect logs from every application instance, review infrastructure metrics, and trace failing requests without leaving a single dashboard.</p>
<p>Be honest about which team yours is. If your engineers maintain log pipelines, monitoring stacks, staging parity, and deployment scripts on top of the product they were hired to build, you're paying the infrastructure tax in its most expensive currency: incident time. Unless operating infrastructure is your business, it's overhead that you can hand to a platform.</p>
<p>A PaaS won't prevent every production bug, but it removes much of the operational complexity that makes those bugs difficult to diagnose. That means less time hunting for information, faster root-cause analysis, quicker recovery during incidents, and more time focused on building software instead of managing infrastructure.</p>
<p>When the next production issue appears, and it inevitably will, you'll spend less time asking, "Why can't I reproduce this?" and more time asking the better question: "Why were we ever doing all of this ourselves?"</p>
<p>Hope you enjoyed this article. You can <a href="https://linkedin.com/in/manishmshiva">connect with me on LinkedIn</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Why "It Worked on My Machine" Still Happens in 2026 ]]>
                </title>
                <description>
                    <![CDATA[ Every engineering team has said it at least once: "It works on my machine." The phrase has become a running joke in software, but it's rarely funny when it happens in production. A feature passes ever ]]>
                </description>
                <link>https://www.freecodecamp.org/news/why-it-worked-on-my-machine-still-happens-in-2026/</link>
                <guid isPermaLink="false">6a57cee36dcb86f0029afe5f</guid>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ PaaS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ deployment ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Manish Shivanandhan ]]>
                </dc:creator>
                <pubDate>Wed, 15 Jul 2026 18:18:11 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/8435d1c8-af34-4cff-8891-087ac2a3ad9d.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Every engineering team has said it at least once: "It works on my machine."</p>
<p>The phrase has become a running joke in software, but it's rarely funny when it happens in production.</p>
<p>A feature passes every local test, the pull request gets approved, the deployment finishes successfully, and then users start reporting failures.</p>
<p>On-call engineers get paged. Incident channels fill up. A fix that took ten minutes to write takes four hours to trace back to a missing environment variable or a runtime version mismatch nobody noticed.</p>
<p>The strange part is that this still happens in 2026.</p>
<p>Modern development has better tooling than ever. Containers, automated testing, cloud infrastructure, CI/CD pipelines, infrastructure as code, and AI coding assistants have all made building software considerably faster.</p>
<p>And yet engineering teams running customer-facing applications continue to lose significant time chasing bugs that only appear outside a developer's laptop.</p>
<p>One <a href="https://queue.acm.org/detail.cfm?id=3068754/">industry survey</a> found that developers spend roughly 40% of their time on tasks unrelated to writing features,&nbsp; and environment debugging is a leading culprit.</p>
<p>The reason isn't that engineers are careless. Most software doesn't fail because of bad code. It fails because code runs inside an environment, and those environments are rarely identical.</p>
<p>The gap between a developer's laptop and a production cluster is still one of the most consistent sources of engineering waste these days.</p>
<p>The real question is no longer why this problem exists. Every experienced engineering team understands environment drift. The better question is why so many product teams are still spending engineering time managing it.</p>
<h3 id="heading-what-well-cover">What We'll Cover:</h3>
<ul>
<li><p><a href="#heading-every-machine-tells-a-slightly-different-story">Every Machine Tells a Slightly Different Story</a></p>
</li>
<li><p><a href="#heading-dependencies-are-moving-targets">Dependencies Are Moving Targets</a></p>
</li>
<li><p><a href="#heading-configuration-causes-more-incidents-than-code-does">Configuration Causes More Incidents Than Code Does</a></p>
</li>
<li><p><a href="#heading-the-real-cost-of-managing-multiple-environments">The Real Cost of Managing Multiple Environments</a></p>
</li>
<li><p><a href="#heading-why-are-teams-still-managing-this-themselves-in-2026">Why Are Teams Still Managing This Themselves in 2026?</a></p>
</li>
<li><p><a href="#heading-local-success-doesnt-reflect-production-conditions">Local Success Doesn't Reflect Production Conditions</a></p>
</li>
<li><p><a href="#heading-why-are-more-engineering-teams-choosing-managed-platforms">Why Are More Engineering Teams Choosing Managed Platforms?</a></p>
</li>
<li><p><a href="#heading-what-a-basic-paas-setup-actually-looks-like">What a Basic PaaS Setup Actually Looks Like</a></p>
</li>
<li><p><a href="#heading-consistency-is-an-ownership-question-not-a-tooling-question">Consistency is an Ownership Question, Not a Tooling Question</a></p>
</li>
</ul>
<h2 id="heading-every-machine-tells-a-slightly-different-story"><strong>Every Machine Tells a Slightly Different Story</strong></h2>
<p>A production application depends on much more than source code.</p>
<p>It depends on the operating system, runtime versions, environment variables, databases, third-party services, networking rules, file permissions, installed libraries, and CPU architecture.</p>
<p>A developer running Node.js 24 LTS may be pairing with a teammate still on 22. One laptop has a newer OpenSSL version installed as a transitive dependency update. Another has a cached <a href="https://www.incredibuild.com/glossary/build-artifacts">build artefact</a> from three months ago that quietly changed behaviour after a library patch.</p>
<p>None of these differences looks significant on their own. Together, they create a local environment that behaves differently from every other environment in the pipeline.</p>
<p>This is how a test suite passes green on a developer's machine and fails in CI twenty minutes later. It's how an application boots cleanly on macOS but crashes on the Debian container your cloud provider runs.</p>
<p>It's why a microservice that handled 500 requests per second last Tuesday starts timing out this Monday after what appeared to be an unrelated dependency bump.</p>
<p>The code hasn't changed. The environment has.</p>
<h2 id="heading-dependencies-are-moving-targets"><strong>Dependencies Are Moving Targets</strong></h2>
<p>Package managers have made software development productive, but they've also dramatically increased the surface area of a running application.</p>
<p>A typical Node.js web application today has between 500 and 1,500 packages in its dependency tree, including indirect dependencies, even when a developer explicitly installs only a handful.</p>
<p>A Python service using common data processing and web frameworks can pull in 200 to 400 packages. Most engineers have no direct relationship with the vast majority of packages their application ships.</p>
<p>When dependency versions aren't locked correctly, two developers installing the same project on the same day can receive materially different software stacks. Lock files like package-lock.json, pnpm-lock.yaml, poetry.lock, Cargo.lock&nbsp;exist precisely to prevent this, and they help. But they're one layer of control in a much larger consistency problem.</p>
<p>Runtime versions still differ. System libraries still differ. Base OS images in containers drift across patch cycles. A Docker image built from node:22 today isn't the same image that gets built in six weeks when the upstream tag has been updated. Teams that don't pin their base images precisely are unknowingly accepting environment drift at the foundation of every deployment.</p>
<h2 id="heading-configuration-causes-more-incidents-than-code-does"><strong>Configuration Causes More Incidents Than Code Does</strong></h2>
<p>Many of the most disruptive production incidents on engineering teams have nothing to do with programming logic.</p>
<p>They come from configuration.</p>
<p>An environment variable is missing in the new deployment target. A database connection string points to staging instead of production. A feature flag is set to true in the developer's .env file but defaults to false in the deployed service, silently disabling a critical code path. An API key was rotated but the secret manager reference was updated in one environment and not the other.</p>
<p>These mistakes are common, and genuinely difficult to prevent,&nbsp;because configuration lives outside the application. It's managed separately, documented inconsistently, and almost never covered by standard test suites.</p>
<p>Post-incident reviews regularly surface configuration drift as the root cause of outages that took hours to diagnose because the application code looked completely correct.</p>
<p>The problem compounds across environments. A team running development, staging, pre-production, and production has four separate configuration states to keep aligned.</p>
<p>When an engineer adds a new environment variable, that change has to propagate through every environment reliably. In practice, it often doesn't. One environment gets missed. An old value lingers. The application behaves differently, and the investigation starts from scratch.</p>
<h2 id="heading-the-real-cost-of-managing-multiple-environments"><strong>The Real Cost of Managing Multiple Environments</strong></h2>
<p>Engineering leadership often underestimates how much time is consumed by environment management, not because it's hard to observe, but because it's distributed across dozens of small tasks that never appear as line items.</p>
<p>Someone updates the Node runtime in the base Docker image and spends an afternoon chasing a downstream test failure that turned out to be a transitive dependency incompatibility.</p>
<p>Someone provisions a new staging environment and spends a day replicating the production configuration by hand. Someone rotates credentials, misses one service, and triggers a silent failure that takes until the next deployment cycle to surface. Someone joins the team and spends the first two days getting a local environment running instead of shipping work.</p>
<p>Estimates from engineering productivity research suggest that infrastructure and environment-related tasks consume between 15 - 25% of total engineering capacity at companies that own their own deployment infrastructure. For a team of ten engineers, that's effectively two to three people running hard and producing no customer-facing output.</p>
<p>This is the cost that doesn't appear on sprint boards. It lives in Slack threads, in incident retrospectives, and in the quiet acknowledgement that the team is slower than it should be.</p>
<p>None of this work appears on a roadmap. Customers never ask for it. It doesn’t create differentiation. Yet product teams spend hundreds of engineering hours every year maintaining consistency between environments simply to keep software deployable. Environment drift isn't just a reliability problem. It's an engineering capacity problem.</p>
<h2 id="heading-why-are-teams-still-managing-this-themselves-in-2026"><strong>Why Are Teams Still Managing This Themselves in 2026?</strong></h2>
<p>Given all of this, the reasonable question is why so many engineering teams are still owning this complexity directly.</p>
<p>Part of the answer is inertia. Teams that built their infrastructure several years ago, when Kubernetes was the obvious answer to every scaling question and "we control our own stack" felt like a competitive advantage, now maintain that infrastructure because changing it has a cost.</p>
<p>The investment is already made. The tooling is already familiar. The pain is distributed and chronic rather than acute, which makes it easier to absorb than to address.</p>
<p>Part of the answer is organisational habit. Hiring a platform or DevOps engineer to manage infrastructure feels like the right response to environmental problems. But that engineer becomes responsible for maintaining the consistency layer indefinitely. Patching base images, updating runtime versions, managing certificate renewals, and debugging networking issues across environments, rather than delivering product leverage.</p>
<p>Part of the answer is a belief that more control produces better outcomes. Running your own infrastructure gives complete visibility into every configuration decision.</p>
<p>But complete control also means complete responsibility. Every decision the platform team makes is a decision the platform team must maintain, document, and revisit every time something upstream changes.</p>
<p>Most product engineering teams aren't in the infrastructure business. They're in the business of building software for customers, and every hour spent on environment consistency is an hour not spent on that.</p>
<p>The honest answer is that many teams are managing this complexity themselves because they haven't yet found a clear path to stopping.</p>
<h2 id="heading-local-success-doesnt-reflect-production-conditions"><strong>Local Success Doesn't Reflect Production Conditions</strong></h2>
<p>One consistent failure mode is treating a passing local test as a signal that a deployment is safe.</p>
<p>Production environments impose conditions that development machines never encounter. A service that starts cleanly on a laptop with no concurrent users will behave differently when handling 2,000 requests per second with three application instances competing for a shared database connection pool.</p>
<p>A background job that completes in milliseconds locally may time out in production when it runs simultaneously with twelve other jobs against a database under real write load.</p>
<p>Staging environments exist to surface these differences before they reach users. But staging only provides value when it actually resembles production, like the same infrastructure, the same runtime versions, the same configuration shape, same network topology.</p>
<p>Many teams treat staging as a best-effort approximation. Over time, configuration drift between staging and production means that staging stops catching the failures it was designed to catch. Teams end up discovering environment-related issues in production anyway, which is the worst place to find them.</p>
<p>Maintaining genuine parity across three or four environments is expensive and requires continuous attention. Infrastructure updates must be applied uniformly. Runtime versions must stay synchronised. Configuration must be propagated reliably.</p>
<p>Without active discipline, staging drifts away from production, and the safety net disappears.</p>
<h2 id="heading-why-are-more-engineering-teams-choosing-managed-platforms"><strong>Why Are More Engineering Teams Choosing Managed Platforms?</strong></h2>
<p>At some point, every engineering organisation has to ask a more fundamental question. Should we keep investing engineering time into maintaining environments, or should we move that responsibility to a platform built for it?</p>
<p>This is the context in which <a href="https://www.freecodecamp.org/news/my-team-s-experience-moving-from-aws-to-a-paas/">Platform as a Service</a> has become a more serious consideration for teams that previously managed their own infrastructure.</p>
<p>A well-designed PaaS doesn't remove engineering responsibility. It relocates it.</p>
<p>Developers still write code. They still define environment variables and build processes. They still decide what their application needs. The difference is that the platform provides a consistent, maintained runtime across every environment like development, staging, and production, without the team owning the underlying infrastructure.</p>
<p>The same application definition runs everywhere. Environment parity becomes a property of the platform rather than a discipline the team has to enforce continuously.</p>
<p>This matters most to engineering teams with real deployment velocity, the teams shipping multiple times per day, running several services, and operating with the expectation that deployments are predictable.</p>
<p>When the platform standardises the environment, deployments stop being experiments. Engineers stop discovering production-only failures at the worst possible time.</p>
<p>The operational tradeoff is real. Some organisations require control over their infrastructure for compliance, regulatory, or architectural reasons that a PaaS can't accommodate. But many teams that believe they need that control have never closely examined the cost of maintaining it.</p>
<p>The question isn't whether owning infrastructure gives you control. It's whether that control is producing outcomes that justify the engineering capacity it consumes.</p>
<h2 id="heading-what-a-basic-paas-setup-actually-looks-like"><strong>What a Basic PaaS Setup Actually Looks Like</strong></h2>
<p>The argument for a managed platform is easier to evaluate with a concrete picture of what adopting one involves. The details vary across providers like Render, Railway, Sevalla, etc, but the setup's shape is remarkably consistent and smaller than most teams expect.</p>
<p><strong>Step 1: Connect your repository.</strong> Every mainstream PaaS starts from your Git repository. You authorise the platform against GitHub or GitLab, point it at a repo, and choose a branch to deploy from. From that moment, the platform watches for pushes. There's no CI pipeline to write for the basic case, since build-and-deploy on push is the default behaviour.</p>
<img src="https://cdn.hashnode.com/uploads/covers/66c6d8f04fa7fe6a6e337edd/46fdb46c-5b94-408c-ad69-bfca34992776.png" alt="Connect repository" style="display:block;margin:0 auto" width="1000" height="825" loading="lazy">

<p><strong>Step 2: Define the application, once, in a file.</strong> Instead of configuring servers, you describe what your application is: the runtime, the build command, the start command, and the services it needs. Most platforms let you do this through a dashboard, but the better practice is a declarative file that lives in the repo.</p>
<pre><code class="language-yaml">services:
  - type: web
    name: my-api
    runtime: node
    buildCommand: npm ci
    startCommand: npm run start
    envVars:
      - key: DATABASE_URL
        fromDatabase:
          name: my-api-db
          property: connectionString
      - key: NODE_ENV
        value: production

databases:
  - name: my-api-db
    plan: basic
</code></pre>
<p>This file is the payoff of the whole model. It's the single source of truth for how the application runs, it's version-controlled alongside the code, and, critically, it's the <em>same definition</em> in every environment.</p>
<p>The drift described earlier in this article, where staging quietly diverges from production, has nowhere to live, because there is no second copy of the environment to fall out of sync.</p>
<p><strong>Step 3: Set your environment variables in the platform, not in files.</strong> Secrets and configuration move out of scattered <code>.env</code> files and into the platform's environment settings, scoped per environment.</p>
<img src="https://cdn.hashnode.com/uploads/covers/66c6d8f04fa7fe6a6e337edd/153f83a9-7aab-4f08-9f19-f6d73b7ccafa.png" alt="Environment variables" style="display:block;margin:0 auto" width="1000" height="293" loading="lazy">

<p>When an engineer adds a new variable, the platform surfaces it in one place rather than requiring a manual update across four deployment targets. Most platforms also support environment groups, so shared configuration is defined once and inherited.</p>
<p><strong>Step 4: Attach managed services.</strong> Databases, caches, and cron jobs are provisioned by the platform rather than installed and patched by your team.</p>
<p>In the example above, the database is declared in the same file as the application, and its connection string is injected automatically, so there's no connection string to copy incorrectly into staging.</p>
<p><strong>Step 5: Push, and let preview environments do the rest.</strong> This is where the parity argument becomes tangible. Most modern PaaS providers spin up a preview environment for every pull request: a full, disposable copy of the application, built from the same definition file, running on the same infrastructure as production.</p>
<img src="https://cdn.hashnode.com/uploads/covers/66c6d8f04fa7fe6a6e337edd/2867152a-233a-4c12-91d3-3f05b56f79e2.png" alt="Deployment" style="display:block;margin:0 auto" width="1000" height="483" loading="lazy">

<p>"Works on my machine" stops being the standard of evidence, because every reviewer is looking at the code running in a production-shaped environment before it merges. When the PR closes, the environment is destroyed.</p>
<p>That's the whole setup. For a typical web service, going from repository to a deployed, auto-updating application with a managed database takes an afternoon, not a quarter.</p>
<p>For teams with existing infrastructure, the sensible starting point isn't a migration project. It's one service , ideally something low-risk and self-contained, like an internal tool or a background worker.</p>
<p>Run it on a platform for a month, compare the operational load against its Kubernetes-hosted siblings, and let the result inform the larger decision. Most teams that make this comparison discover the question isn't whether the platform can handle their workload. It's how much of their engineering week they'd been spending to get a worse version of the same guarantee.</p>
<h2 id="heading-consistency-is-an-ownership-question-not-a-tooling-question"><strong>Consistency is an Ownership Question, Not a Tooling Question</strong></h2>
<p>"It worked on my machine" gets framed as a process problem, or a testing problem, or occasionally a culture problem. The real framing is more useful: it's an ownership problem.</p>
<p>Every difference between environments like runtime versions, dependency trees, configuration values, and infrastructure state increases the probability that software behaves unexpectedly in production. The conventional response is to invest in better tooling: stricter lock files, more comprehensive CI, better container discipline, more thorough staging.</p>
<p>All of these reduce the problem. None of them eliminates the underlying dynamic, which is that the team is responsible for the consistency of every environment it owns.</p>
<p>The teams that have largely solved deployment reliability in 2026 aren't necessarily the ones with the most sophisticated infrastructure. Many of them are the ones that have reduced the number of environments they own and maintain.</p>
<p>They have moved infrastructure decisions to platforms designed to handle them, and redirected that engineering capacity toward problems that are actually differentiated: the product, the performance, the reliability of the application itself.</p>
<p>Environment consistency is a solvable problem. The remaining question is ownership. Every product team must decide whether maintaining infrastructure is part of its competitive advantage or simply an operational burden it has accepted over time. More engineering teams are concluding that their advantage comes from shipping product, not managing environments.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ My Team's Experience Moving from AWS to a PaaS ]]>
                </title>
                <description>
                    <![CDATA[ Most product teams assume infrastructure ownership is simply part of building software. We did too. It wasn’t until we measured how much engineering time was disappearing into operational work that we ]]>
                </description>
                <link>https://www.freecodecamp.org/news/my-team-s-experience-moving-from-aws-to-a-paas/</link>
                <guid isPermaLink="false">6a442bf6f75ef9bd6e0bfc7f</guid>
                
                    <category>
                        <![CDATA[ infrastructure ]]>
                    </category>
                
                    <category>
                        <![CDATA[ PaaS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Manish Shivanandhan ]]>
                </dc:creator>
                <pubDate>Tue, 30 Jun 2026 20:49:58 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/01d97a10-ea77-49e7-a8d9-1471683948b7.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Most product teams assume infrastructure ownership is simply part of building software. We did too. It wasn’t until we measured how much engineering time was disappearing into operational work that we realised how expensive that assumption had become.</p>
<p>During a quarterly planning session, one of our engineers asked a question nobody on the team had thought to ask directly before: “How much of our time is actually going into infrastructure, versus building things people use?”</p>
<p>It wasn’t a rhetorical question. We pulled up our sprint history, our incident logs, and our calendars, and tried to answer it honestly.</p>
<p>We were a 7-person internal tooling team inside a larger enterprise organisation. Our mandate was straightforward: make other teams across the company faster through workflow automation, internal dashboards, and integrations between internal systems.</p>
<p>Our <a href="https://aws.amazon.com/">Amazon Web Services (AWS)</a> environment wasn’t poorly built. It was, by most standards, mature infrastructure. Containerised services on ECS, automated deployments through GitHub Actions, CloudWatch observability, and properly scoped IAM roles across environments. Nothing about it would have raised concerns in an architecture review.</p>
<p>What it cost us wasn’t visible on an invoice. It was visible in calendars, in context-switching, and in how often “infrastructure work” quietly displaced the backlog we were actually accountable for.</p>
<p>That conversation eventually led us to evaluate and migrate to <a href="http://sevalla.com/">Sevalla</a>, a Platform-as-a-Service infrastructure control for operational simplicity. The migration took three weeks. The effects were measurable within a month.</p>
<p>In this article, we'll walk through what our AWS setup looked like before migrating, what the migration process actually involved, the specific metrics that changed afterwards, and the trade-offs we accepted along the way.</p>
<h3 id="heading-what-well-cover">What We'll Cover:</h3>
<ul>
<li><p><a href="#heading-before-the-migration">Before the Migration</a></p>
</li>
<li><p><a href="#heading-the-number-that-started-the-conversation">The Number That Started the Conversation</a></p>
</li>
<li><p><a href="#heading-the-deployment-process-what-reasonably-automated-actually-meant">The Deployment Process: What “Reasonably Automated” Actually&nbsp;Meant</a></p>
</li>
<li><p><a href="#heading-what-the-migration-actually-involved">What the Migration Actually&nbsp;Involved</a></p>
</li>
<li><p><a href="#heading-what-changed-after-the-migration">What Changed After the Migration</a></p>
<ul>
<li><p><a href="#heading-deployment-time-dropped-from-12-minutes-to-3-minutes">Deployment time dropped from ~12 minutes to ~3&nbsp;minutes</a></p>
</li>
<li><p><a href="#heading-any-engineer-could-deploy-confidently-on-day-one">Any engineer could deploy confidently on day&nbsp;one</a></p>
</li>
<li><p><a href="#heading-rollbacks-went-from-a-12-minute-manual-process-to-a-30-second-action">Rollbacks went from a 12-minute manual process to a 30-second action</a></p>
</li>
<li><p><a href="#heading-infrastructure-maintenance-time-dropped-to-approximately-23-hours-per-week">Infrastructure maintenance time dropped to approximately 2–3 hours per&nbsp;week</a></p>
</li>
<li><p><a href="#heading-log-visibility-improved-without-any-additional-tooling">Log visibility improved without any additional tooling</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-what-we-gave-up">What We Gave&nbsp;Up</a></p>
</li>
<li><p><a href="#heading-the-actual-lesson">The Actual&nbsp;Lesson</a></p>
</li>
</ul>
<h2 id="heading-before-the-migration">Before the Migration</h2>
<p>Our AWS setup was respectable. We weren’t running something embarrassingly manual. We had:</p>
<ul>
<li><p>ECS for container orchestration</p>
</li>
<li><p>RDS for databases</p>
</li>
<li><p>CloudWatch for logs and metrics</p>
</li>
<li><p>A CI/CD pipeline through GitHub Actions</p>
</li>
<li><p>IAM roles managed across environments</p>
</li>
<li><p><a href="https://aws.amazon.com/cloudformation/">CloudFormation</a> templates maintained by one senior engineer</p>
</li>
</ul>
<p>It worked. Deployments were automated. The system was stable.</p>
<p>The problem wasn’t that anything was broken. The problem was what it cost us to keep it running smoothly.</p>
<h2 id="heading-the-number-that-started-the-conversation">The Number That Started the Conversation</h2>
<p>During a quarterly planning session, we tried to honestly account for where engineering time was going.</p>
<p>We estimated that across the team, roughly 12–15 hours per week were being spent on infrastructure-related work that wasn’t directly delivering value to internal users. This included:</p>
<ul>
<li><p>Deployment pipeline maintenance and debugging (~4 hrs/week)</p>
</li>
<li><p>CloudWatch log investigation and alert tuning (~3 hrs/week)</p>
</li>
<li><p>IAM permissions management and access reviews (~2 hrs/week)</p>
</li>
<li><p>Dependency updates, security patches for infrastructure components (~2 hrs/week)</p>
</li>
<li><p>Ad hoc incidents, environment drift, cost anomaly investigations (~3–4 hrs/week)</p>
</li>
</ul>
<p>At a fully-loaded engineer cost, 12–15 hours per week is the equivalent of roughly one-third of a full-time engineer, every week, spent on keeping the lights on rather than building anything.</p>
<p>For a team whose backlog was already longer than we could realistically tackle, that number was hard to justify.</p>
<h2 id="heading-the-deployment-process-what-reasonably-automated-actually-meant">The Deployment Process: What “Reasonably Automated” Actually&nbsp;Meant</h2>
<p>Our deployment pipeline was good by most standards. Push to <code>main</code>, GitHub Actions triggered a build, pushed an image to ECR, and updated the ECS service. On a good day, a deployment took about 12 minutes from merge to live.</p>
<p>But “reasonably automated” came with caveats.</p>
<p>Only one engineer fully understood the pipeline. If something failed mid-deployment, like a task definition mismatch, an IAM permission error, or a CloudFormation stack conflict, most of the team would either wait for that engineer or spend significant time reading AWS documentation to diagnose it themselves.</p>
<p>Rollbacks were manual. There was no clean one-click rollback. Rolling back meant redeploying the previous image tag, which required knowing what that tag was, triggering the pipeline again, and waiting another 12 minutes. In an incident, those 12 minutes mattered.</p>
<p>Environment parity was fragile. We had staging and production environments. Keeping them consistent required discipline and periodic reconciliation. Configuration drift happened more than we’d like to admit, and it occasionally caused releases to behave differently in production than they had in staging.</p>
<p>New team members couldn’t deploy confidently. Onboarding a new engineer to the deployment process took the better part of a day, and most new hires remained hesitant to trigger deployments independently for weeks. The pipeline was automated, but the knowledge wasn’t.</p>
<h2 id="heading-what-the-migration-actually-involved">What the Migration Actually&nbsp;Involved</h2>
<p>We moved over the course of about three weeks, migrating services incrementally rather than cutting over all at once.</p>
<p>The largest time investment was translating our environment variable configuration and secrets management from AWS Parameter Store into Sevalla’s environment configuration. That took roughly half a day.</p>
<p>The CI/CD migration was straightforward. We replaced our ECS deployment step with Sevalla’s Git-connected deployment. The GitHub integration picked up our repository directly.</p>
<p>Database migration was the most careful part. We ran both databases in parallel for two weeks, verified data consistency, then cut over DNS. There was no data loss, and no downtime.</p>
<p>Total migration effort across the team: approximately 40 hours spread over three weeks, mostly concentrated in two engineers.</p>
<h2 id="heading-what-changed-after-the-migration">What Changed After the Migration</h2>
<h3 id="heading-deployment-time-dropped-from-12-minutes-to-3-minutes">Deployment time dropped from ~12 minutes to ~3&nbsp;minutes</h3>
<p>This wasn’t the most important change, but it was the most immediately visible one. Faster deployments meant faster feedback loops. A fix could be in production and verified within minutes rather than waiting out a build cycle.</p>
<p>Over a typical week with 8–10 deployments, that’s roughly 90 minutes of cumulative waiting time recovered, per week.</p>
<h3 id="heading-any-engineer-could-deploy-confidently-on-day-one">Any engineer could deploy confidently on day&nbsp;one</h3>
<p>This was the change that mattered most operationally. The deployment process became visible, documented by the interface itself, and required no specialist knowledge to operate. A new engineer joining the team could deploy their first change independently on their first day.</p>
<p>The informal “deployment gatekeeper” role that had quietly formed around our most AWS-experienced engineer effectively dissolved.</p>
<h3 id="heading-rollbacks-went-from-a-12-minute-manual-process-to-a-30-second-action">Rollbacks went from a 12-minute manual process to a 30-second action</h3>
<p>Every deployment in Sevalla retains a one-click rollback to the previous build. During the first month after migration, we used this twice: once for a regression we caught quickly, and once during a failed database migration we immediately needed to reverse.</p>
<p>Both incidents that previously would have required hours of manual intervention were resolved in under a minute.</p>
<h3 id="heading-infrastructure-maintenance-time-dropped-to-approximately-23-hours-per-week">Infrastructure maintenance time dropped to approximately 2–3 hours per&nbsp;week</h3>
<p>We no longer maintain IAM roles, CloudWatch alerts, CloudFormation templates, or ECS task definitions. The infrastructure surface area we own shrank dramatically.</p>
<p>Our estimate of 12–15 hours per week of infrastructure work fell to roughly 2–3 hours per week . It now involves  primarily monitoring application behaviour and reviewing build logs. That’s a recovery of approximately 10 hours per week of engineering time redirected toward the actual backlog.</p>
<p>Over a quarter, that’s roughly 130 hours, or about three full working weeks, returned to product work.</p>
<p>Looking back, we had quietly become a platform team. Not because we intended to, but because every infrastructure decision created more infrastructure to own.</p>
<h3 id="heading-log-visibility-improved-without-any-additional-tooling">Log visibility improved without any additional tooling</h3>
<p>One outcome we didn’t anticipate: production visibility got better even though we invested less in it.</p>
<p>On AWS, meaningful log analysis required CloudWatch Insights queries, proper log group configuration, and knowing where to look. Useful observability required deliberate setup effort.</p>
<p>On Sevalla, build logs, runtime logs, and deployment history are accessible directly from the dashboard without configuration. When something went wrong in production, the time from “something is broken” to “here is what happened” dropped from 10–20 minutes of searching across tools to under 2 minutes in most cases.</p>
<h2 id="heading-what-we-gave-up">What We Gave&nbsp;Up</h2>
<p>Intellectual honesty requires listing the trade-offs.</p>
<p>First, we have less infrastructure flexibility. If we needed custom networking topology, specialised compute instances, or fine-grained storage configuration, Sevalla wouldn't cover those requirements. For an internal tooling team, none of those needs has materialised. But they could.</p>
<p>Also, some AWS-native integrations required reworking. We used a few Lambda functions that had to be refactored into services. That added some migration complexity we hadn’t fully anticipated.</p>
<h2 id="heading-the-actual-lesson">The Actual&nbsp;Lesson</h2>
<p>The migration confirmed something that’s easy to miss when you’re inside it: the cost of infrastructure ownership for a product team isn’t primarily the cloud bill. It’s the engineering attention.</p>
<p>For our team, 10 hours per week of recovered time across a 7-person team meant a 28% increase in capacity available for work that users actually care about. That’s not a marginal improvement. It’s a meaningful change in what the team can realistically ship.</p>
<p>That outcome isn’t specific to Sevalla. Any infrastructure simplification that genuinely reduces operational burden would produce a similar result.</p>
<p>The question worth asking isn’t whether your team <em>can</em> manage infrastructure. It’s whether managing infrastructure is the best use of the engineering capacity you have.</p>
<p>For an internal tooling team whose value is measured entirely by what it ships, not by how it deploys, the answer, for us, was clearly no.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ The Tradeoff That Slows Production Teams Down: Flexibility vs Actually Shipping ]]>
                </title>
                <description>
                    <![CDATA[ Every company says it wants speed. Roadmaps talk about velocity. Leadership meetings talk about reducing cycle time. Quarterly goals talk about faster execution and quicker releases. Every business wa ]]>
                </description>
                <link>https://www.freecodecamp.org/news/the-tradeoff-that-slows-production-teams-down-flexibility-vs-actually-shipping/</link>
                <guid isPermaLink="false">6a19ccc19e433f18f384364b</guid>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ production ]]>
                    </category>
                
                    <category>
                        <![CDATA[ deployment ]]>
                    </category>
                
                    <category>
                        <![CDATA[ PaaS ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Manish Shivanandhan ]]>
                </dc:creator>
                <pubDate>Fri, 29 May 2026 17:28:33 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/495a017a-0f6f-4e3b-8d55-6c3854917c51.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Every company says it wants speed.</p>
<p>Roadmaps talk about velocity. Leadership meetings talk about reducing cycle time. Quarterly goals talk about faster execution and quicker releases.</p>
<p>Every business wants teams moving faster.</p>
<p>Then many of those same companies make a decision that quietly slows everything down. They optimise for infrastructure flexibility instead of product delivery.</p>
<p>It sounds reasonable in the beginning. Teams want control. Engineers want options. Platform architects want systems that can support every future scenario.</p>
<p>So production teams start building infrastructure ecosystems around themselves.</p>
<p>Deployment pipelines get built from scratch. Cloud resources become heavily customised. Internal platforms gain endless knobs, switches, and configuration layers. New projects begin with architecture discussions instead of customer problems.</p>
<p>Months later, software delivery slows down.</p>
<p>Product teams miss timelines. Releases move out by quarters. Customer feedback arrives later. Competitors keep shipping.</p>
<p>The tradeoff hiding underneath all of this is simple. Teams choose flexibility over actually shipping.</p>
<p>And beyond a certain point, flexibility becomes one of the most expensive forms of organisational drag a company can create.</p>
<h3 id="heading-what-well-cover">What We'll Cover:</h3>
<ul>
<li><p><a href="#heading-the-myth-that-more-flexibility-creates-better-production-systems">The Myth That More Flexibility Creates Better Production Systems</a></p>
</li>
<li><p><a href="#heading-infrastructure-ownership-quietly-becomes-a-second-business">Infrastructure Ownership Quietly Becomes a Second Business</a></p>
</li>
<li><p><a href="#heading-the-real-cost-is-delayed-customer-learning">The Real Cost Is Delayed Customer Learning</a></p>
</li>
<li><p><a href="#heading-paas-changes-the-optimisation-function">PaaS Changes the Optimisation Function</a></p>
</li>
<li><p><a href="#heading-the-best-production-teams-remove-decisions">The Best Production Teams Remove Decisions</a></p>
</li>
<li><p><a href="#heading-custom-infrastructure-usually-solves-problems-nobody-has-yet">Custom Infrastructure Usually Solves Problems Nobody Has Yet</a></p>
</li>
<li><p><a href="#heading-the-real-competitive-advantage-is-shipping-faster">The Real Competitive Advantage Is Shipping Faster</a></p>
</li>
<li><p><a href="#heading-when-paas-might-not-be-the-right-choice">When PaaS Might Not Be the Right Choice</a></p>
</li>
<li><p><a href="#heading-stop-building-infrastructure-businesses-by-accident">Stop Building Infrastructure Businesses By Accident</a></p>
</li>
</ul>
<h2 id="heading-the-myth-that-more-flexibility-creates-better-production-systems">The Myth That More Flexibility Creates Better Production Systems</h2>
<p>Engineering teams love optionality. The logic sounds convincing.</p>
<p>If infrastructure is fully customizable, teams can adapt to future requirements. If deployment systems are built internally, every use case can be supported. If every layer is configurable, engineers can optimise for unique situations.</p>
<p>This feels like responsible engineering. But it often becomes expensive business behaviour.</p>
<p>Most production teams massively overestimate how often they need deep infrastructure flexibility.</p>
<p>What actually happens becomes predictable.</p>
<p>A product team starts a new initiative. Instead of shipping an early version and learning from customers, discussions begin.</p>
<ul>
<li><p>Should Kubernetes clusters be organised by team or service?</p>
</li>
<li><p>Should CI/CD use GitHub Actions or Jenkins?</p>
</li>
<li><p>Should secrets management use Vault or cloud-native tooling?</p>
</li>
<li><p>Should observability use Prometheus or Datadog?</p>
</li>
<li><p>Should deployment strategies use canary releases, <a href="https://www.redhat.com/en/topics/devops/what-is-blue-green-deployment">blue-green deployments</a>, or something custom?</p>
</li>
</ul>
<p>Weeks disappear. No customer sees anything. No assumptions get tested. No learning happens.</p>
<p>Meanwhile, product managers wait. Leadership waits. Customers wait.</p>
<p>Even with <a href="https://sevalla.com/blog/building-apps-with-sevalla-and-claude-code/">agentic coding tools</a> like Claude generating code, scaffolding systems and accelerating implementation, teams still lose speed when every output collides with infrastructure decisions and deployment debates.</p>
<p>The problem isn't technology. The problem is optimising around theoretical future flexibility instead of present business outcomes.</p>
<p>Software creates value when customers use it. Everything else is support work.</p>
<h2 id="heading-infrastructure-ownership-quietly-becomes-a-second-business">Infrastructure Ownership Quietly Becomes a Second Business</h2>
<p>Traditional deployment models accidentally create a dangerous pattern: companies think they are building products. Slowly, they start building infrastructure organisations.</p>
<p>Production teams provision servers. Then networking. Then IAM systems. Then deployment pipelines. Then, observability layers. Then secrets management. Then autoscaling. Then rollback systems.</p>
<p>Every decision feels reasonable in isolation. But collectively, teams create an operational machine they now own forever.</p>
<p>And ownership is where the hidden cost appears.</p>
<p>Because infrastructure work doesn't end after launch. It expands. Pipelines need maintenance. Security policies change. Monitoring systems require tuning. Platform dependencies break. Internal tooling needs upgrades.</p>
<p>Production teams gradually spend more time maintaining systems around software than improving software itself.</p>
<p>This creates a strange situation: highly paid engineers become caretakers for infrastructure instead of builders of customer value.</p>
<p>No customer purchases a product because deployment pipelines have become elegant. No customer upgrades because IAM policies are beautifully designed. No competitor loses market share because Kubernetes YAML looks sophisticated.</p>
<p>Customers care about products solving problems. Infrastructure only matters when it slows product delivery.</p>
<p>And infrastructure ownership creates endless opportunities for that to happen.</p>
<h2 id="heading-the-real-cost-is-delayed-customer-learning">The Real Cost Is Delayed Customer Learning</h2>
<p>The biggest cost of infrastructure complexity isn't engineering effort. It's delayed learning.</p>
<p>Software companies win through feedback loops. Teams ship something. Customers react. Teams learn. Products improve.</p>
<p>The faster this cycle operates, the stronger the company becomes.</p>
<p>Infrastructure work interrupts that loop. Every month spent building deployment systems is a month where customers aren't using new features. Every quarter spent designing internal platforms delays customer feedback. Every architecture discussion delays real market signals.</p>
<p>This is where many organisations misunderstand velocity.</p>
<p>They look at sprint metrics. They measure tickets completed. They count engineering output.</p>
<p>But business speed isn't measured through internal activity. Business speed measures how quickly ideas become customer reality.</p>
<p>Infrastructure ownership slows that process dramatically. And slower learning creates slower companies.</p>
<h2 id="heading-paas-changes-the-optimisation-function">PaaS Changes the Optimisation Function</h2>
<p>This is where <a href="https://www.freecodecamp.org/news/from-metrics-to-meaning-how-paas-helps-developers-understand-production/">Platform as a Service</a> changes the equation.</p>
<p>PaaS forces organisations to optimise around shipping rather than infrastructure ownership. That shift matters more than most teams realise.</p>
<p>Instead of spending weeks designing deployment architecture, production teams connect repositories and deploy.</p>
<p>Instead of building pipelines manually, pipelines already exist.</p>
<p>Instead of designing scaling systems, scaling becomes infrastructure behaviour rather than engineering work.</p>
<p>Instead of repeatedly building foundations, infrastructure becomes a utility.</p>
<p>That sounds simple. It should be simple. Deployment should feel boring. But the fact that deployment often becomes a major organisational project is usually evidence of unnecessary complexity rather than unavoidable complexity.</p>
<p>PaaS providers remove entire categories of decisions. And while many engineers see that as a compromise, it's often the opposite.</p>
<p>Constraints create speed. Speed creates learning. Learning creates better products.</p>
<h2 id="heading-the-best-production-teams-remove-decisions">The Best Production Teams Remove Decisions</h2>
<p>There's a common misconception that elite engineering organisations maximise options. The opposite is often true.</p>
<p>High-performing production teams aggressively eliminate decisions. They standardise. They create defaults. They remove unnecessary choices.</p>
<p>Because every decision carries a cost.</p>
<p>Cognitive load grows. Coordination increases. Meetings multiply. Dependencies expand. Eventually, the workaround software becomes larger than the software itself.</p>
<p>PaaS systems follow a different philosophy. They intentionally reduce optionality.</p>
<p>That reduction creates focus. And focus creates product velocity. Product velocity creates business outcomes.</p>
<p>The chain is straightforward. Too many organisations break it by introducing infrastructure ownership far too early.</p>
<h2 id="heading-custom-infrastructure-usually-solves-problems-nobody-has-yet">Custom Infrastructure Usually Solves Problems Nobody Has Yet</h2>
<p>One of the most expensive habits in software companies is solving future problems before current ones exist.</p>
<p>Teams build for scale before scale exists. They create multi-region architectures before international users arrive. They build deployment frameworks before deployment pain appears.</p>
<p>This usually comes from good intentions. Engineers want to avoid future rewrites. But the irony is that premature flexibility creates an immediate business slowdown.</p>
<p>A startup with twenty engineers shouldn't operate like a company with ten thousand engineers. Yet many production teams copy infrastructure patterns from giant technology firms.</p>
<p>What gets ignored is context. Large technology companies have entire platform teams maintaining internal systems. They have thousands of engineers supporting infrastructure investments.</p>
<p>Most companies do not.</p>
<p>Copying technical architecture without copying organisational scale creates enormous inefficiency.</p>
<p>PaaS acts as protection against this behaviour. It prevents teams from accidentally becoming infrastructure companies before they become successful product companies.</p>
<h2 id="heading-the-real-competitive-advantage-is-shipping-faster">The Real Competitive Advantage Is Shipping Faster</h2>
<p>Companies rarely lose because infrastructure flexibility was insufficient. They lost because competitors learned faster.</p>
<p>Speed matters. Not speed in sprint or <a href="https://linear.app/">linear dashboards</a>. Not speed in story points.</p>
<p>Actual speed. The ability to move ideas into production quickly. The ability to test assumptions rapidly. The ability to learn continuously.</p>
<p>Shipping creates learning. Learning creates improvement. Improvement creates advantage.</p>
<p>Infrastructure complexity interrupts this loop. PaaS strengthens it.</p>
<p>This is why deployment decisions should never be treated as purely technical discussions. They are business decisions.</p>
<p>Infrastructure ownership affects company velocity. Velocity affects market outcomes.</p>
<p>The argument isn't about servers. The argument is about competitive speed.</p>
<h2 id="heading-when-paas-might-not-be-the-right-choice">When PaaS Might Not Be the Right Choice</h2>
<p>There are situations where PaaS can become limiting.</p>
<p>Organisations with highly specialised infrastructure requirements may require direct control over networking, security layers, hardware optimisation, or deployment behaviour.</p>
<p>Some industries have regulatory requirements that create unusually specific infrastructure needs.</p>
<p>Large organisations with mature platform engineering teams may also justify custom infrastructure investments.</p>
<p>There are also cases where platform costs become meaningful at very large scale.</p>
<p>These scenarios exist. But many companies use edge cases as justification years before they become relevant. They prepare for infrastructure problems they may never have while struggling to ship ordinary product releases today.</p>
<p>That sequence creates unnecessary friction.</p>
<h2 id="heading-stop-building-infrastructure-businesses-by-accident">Stop Building Infrastructure Businesses By Accident</h2>
<p>Engineering culture often celebrates flexibility.</p>
<p>Flexibility sounds sophisticated. It sounds future-proof. It sounds like good systems thinking.</p>
<p>But flexibility carries a cost. Every additional option creates complexity. Every additional decision slows movement. Every additional layer creates maintenance work.</p>
<p>Production teams should ask a simpler question. Does this help us ship customer-facing software faster? If the answer is no, it deserves scrutiny.</p>
<p>Too many companies accidentally build infrastructure ecosystems that optimise for hypothetical future needs.</p>
<p>Meanwhile, competitors deploy products, learn from customers and improve faster.</p>
<p>Shipping beats flexibility. And for many production teams, choosing a PaaS is one of the clearest ways to prove it.</p>
<p>Hope you enjoyed this article. You can <a href="https://linkedin.com/in/manishmshiva">connect with me on LinkedIn</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Avoid Rebuilding Infrastructure for Every New Project ]]>
                </title>
                <description>
                    <![CDATA[ Every production engineering team knows the pattern. A new project begins with energy. Product goals are clear. Deadlines are ambitious. Teams want to move quickly and deliver something customers can  ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-avoid-rebuilding-infrastructure-for-every-new-project/</link>
                <guid isPermaLink="false">6a0f78aad8e265f60d5f7b56</guid>
                
                    <category>
                        <![CDATA[ PaaS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Kubernetes ]]>
                    </category>
                
                    <category>
                        <![CDATA[ distributed system ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Manish Shivanandhan ]]>
                </dc:creator>
                <pubDate>Thu, 21 May 2026 21:27:06 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5fc16e412cae9c5b190b6cdd/6c414744-42af-430a-8bbd-76a33b564e4b.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Every production engineering team knows the pattern. A new project begins with energy. Product goals are clear. Deadlines are ambitious. Teams want to move quickly and deliver something customers can use.</p>
<p>Then the real work starts. Infrastructure must be provisioned. CI/CD pipelines need to be set up. Secrets require management. Monitoring needs wiring. Databases need deployment. Logging needs configuration. Security policies need implementation. Networking rules need review.</p>
<p>Weeks disappear before users see anything useful. Many organizations treat this as normal. They call it engineering rigour. They assume this operational setup phase is simply part of software development.</p>
<p>It is not.</p>
<p>For teams already running production systems, rebuilding infrastructure foundations for every new project is organizational waste. It is repetitive operational labour disguised as an engineering discipline.</p>
<p>The uncomfortable question is not, “How can we do this setup faster?” The real question is: why are we still doing it ourselves at all?</p>
<p>This is where <a href="https://www.freecodecamp.org/news/from-metrics-to-meaning-how-paas-helps-developers-understand-production/">Platform as a Service</a> changes the conversation. A good PaaS shifts the starting point from “rebuild the foundations” to “start shipping. Because new projects should begin closer to customer value, not closer to infrastructure assembly.</p>
<p>In this article, we'll look at why many production teams waste time rebuilding the same infrastructure for every new project, how PaaS helps remove that work, and why engineering teams should question if managing complex infrastructure still makes sense for most projects.</p>
<h2 id="heading-what-well-cover">What We'll Cover:</h2>
<ul>
<li><p><a href="#heading-most-teams-were-not-hired-to-build-infrastructure">Most Teams Were Not Hired to Build Infrastructure</a></p>
</li>
<li><p><a href="#heading-aws-primitives-are-not-a-competitive-advantage">AWS Primitives Are Not a Competitive Advantage</a></p>
</li>
<li><p><a href="#heading-most-teams-should-not-be-managing-kubernetes">Most Teams Should Not Be Managing Kubernetes</a></p>
</li>
<li><p><a href="#heading-paas-changes-the-starting-point">PaaS Changes the Starting Point</a></p>
</li>
<li><p><a href="#heading-repetition-creates-hidden-organizational-waste">Repetition Creates Hidden Organizational Waste</a></p>
</li>
<li><p><a href="#heading-standardization-is-usually-faster-than-flexibility">Standardization Is Usually Faster Than Flexibility</a></p>
</li>
<li><p><a href="#heading-platform-teams-become-multipliers">Platform Teams Become Multipliers</a></p>
</li>
<li><p><a href="#heading-easier-starts-create-more-innovation">Easier Starts Create More Innovation</a></p>
</li>
<li><p><a href="#heading-when-specialized-control-actually-matters">When Specialized Control Actually Matters</a></p>
</li>
<li><p><a href="#heading-starting-from-zero-is-a-process-failure">Starting From Zero Is a Process Failure</a></p>
</li>
</ul>
<h2 id="heading-most-teams-were-not-hired-to-build-infrastructure"><strong>Most Teams Were Not Hired to Build Infrastructure</strong></h2>
<p>Software teams exist to solve business problems. Customers do not care whether Kubernetes manifests were structured elegantly. They do not admire carefully designed Terraform modules. They do not celebrate handcrafted networking policies.</p>
<p>Customers care about outcomes. They care about faster onboarding. Better recommendations. Smoother payments. Fewer bugs. Simpler workflows.</p>
<p>Yet many engineering organizations spend huge portions of time doing work customers never see.</p>
<p>Teams repeatedly create deployment pipelines. Configure environments. Manage certificates. Set up observability stacks. Tune infrastructure rules. Assemble cloud primitives.</p>
<p>Infrastructure matters. Reliability matters. Security matters.</p>
<p>The problem is duplication. If every project independently recreates the same operational systems, organizations keep rebuilding internal platforms over and over again without admitting it.</p>
<p>This behaviour has become so normalized that teams barely notice it anymore. But rebuilding the same foundation repeatedly is not operational maturity. It is inefficiency scaled across the organization.</p>
<h2 id="heading-aws-primitives-are-not-a-competitive-advantage"><strong>AWS Primitives Are Not a Competitive Advantage</strong></h2>
<p>Many teams confuse cloud ownership with strategic advantage. Owning Kubernetes clusters does not create differentiation. Managing IAM rules does not create customer value. Writing infrastructure glue code does not strengthen market position.</p>
<p>These are implementation details. Yet many organizations spend extraordinary energy managing them as if they are core business assets.</p>
<p>Some teams effectively become part-time infrastructure companies without realizing it. Their engineers slowly accumulate operational responsibilities until maintaining systems consumes more effort than delivering products.</p>
<p>The outcome becomes predictable. Infrastructure expands. Operational complexity grows. Delivery speed declines. Nobody notices because the pain arrives gradually.</p>
<p>A team starts with one Kubernetes cluster. Then another environment appears. More deployment pipelines emerge. Additional tooling gets layered on top. Logging systems become fragmented. Monitoring evolves differently across products.</p>
<p>Eventually, teams spend increasing amounts of time maintaining systems they never intended to own.</p>
<p>Infrastructure ownership is often not a strategy. It is inertia.</p>
<h2 id="heading-most-teams-should-not-be-managing-kubernetes"><strong>Most Teams Should Not Be Managing Kubernetes</strong></h2>
<p><a href="https://www.freecodecamp.org/news/what-does-k8s-mean-kubernetes-setup-guide/">Kubernetes</a> has become an engineering culture. It appears in architecture diagrams, conference talks, hiring requirements, and internal roadmaps. Its adoption often feels inevitable.</p>
<p>But normalization and necessity are not the same thing. Many organizations adopted Kubernetes because industry momentum made it seem like the default path. Not because they had workloads that required its complexity. But the result is predictable.</p>
<p>Small and medium teams end up managing orchestration systems designed for massive operational environments.</p>
<p>They maintain YAML configurations, networking layers, ingress systems, deployment strategies, and operational tooling stacks before delivering meaningful product value. This has become strangely accepted.</p>
<p>A ten-person engineering team maintaining infrastructure patterns designed for internet-scale organizations should raise serious questions. A small team pretending to be a platform team is an operational dysfunction.</p>
<p>Many companies adopt infrastructure complexity built for organizations operating at a vastly different scale. They inherit the burden without inheriting the benefits.</p>
<h2 id="heading-paas-changes-the-starting-point"><strong>PaaS Changes the Starting Point</strong></h2>
<p><a href="https://www.freecodecamp.org/news/the-hidden-tax-of-infrastructure-why-your-team-shouldn-t-be-running-it-anymore/">Traditional infrastructure</a> approaches force teams to think from the bottom upward. Servers come first. Then operating systems. Then networking. Then deployment systems. Then monitoring. Eventually, applications arrive.</p>
<p>PaaS reverses this sequence. Developers begin with applications and business goals. The platform absorbs operational complexity.</p>
<p>Teams stop asking, “How do we provision resources?” They start asking, “What problem are we solving?” That sounds like a small shift. In practice, it changes everything.</p>
<p>A mature PaaS environment often provides deployment pipelines, integrated observability, databases, scaling behaviour, security controls, and operational standards before a team writes meaningful application logic.</p>
<p>Projects begin with product development rather than infrastructure construction. That dramatically changes time-to-value.</p>
<h2 id="heading-repetition-creates-hidden-organizational-waste"><strong>Repetition Creates Hidden Organizational Waste</strong></h2>
<p>Organizations often underestimate operational waste because repetitive work feels familiar. Setting up a deployment pipeline may consume only a few days. Configuring logging may feel routine. Creating security rules may seem manageable.</p>
<p>No individual task appears expensive. The cost appears when repetition scales.</p>
<p>If ten projects independently spend two weeks rebuilding nearly identical operational systems, months of engineering capacity disappear. Those engineers could have shipped customer capabilities. They could have reduced friction. They could have tested new ideas. Instead, they rebuilt plumbing.</p>
<p>Engineering teams understand leverage in nearly every other area. Nobody rewrites sorting algorithms for every application. Nobody recreates database engines from scratch. Nobody builds networking stacks repeatedly.</p>
<p>Reuse is accepted as basic engineering wisdom. Infrastructure should not receive special treatment. Build once. Reuse many times.</p>
<p>PaaS simply applies software engineering principles to operational systems.</p>
<h2 id="heading-standardization-is-usually-faster-than-flexibility"><strong>Standardization Is Usually Faster Than Flexibility</strong></h2>
<p>Engineering teams often resist standardization because they fear losing control. Every project feels unique. Every system appears different. The desire for flexibility sounds reasonable.</p>
<p>But complete flexibility often creates operational chaos. Different teams deploy applications differently. Logging behaves inconsistently. Monitoring varies across systems. Security implementations drift.</p>
<p>Documentation fragments. Onboarding slows. Incident response becomes harder. Complexity quietly accumulates.</p>
<p>PaaS introduces constraints, and many engineers instinctively resist constraints. They should not. Useful constraints often increase speed.</p>
<p>Predictable deployment patterns reduce confusion. Shared monitoring standards simplify troubleshooting. Consistent environments reduce cognitive overhead.</p>
<p>Developers spend less energy understanding infrastructure differences and more time delivering product functionality.</p>
<p>Consistency compounds.</p>
<h2 id="heading-platform-teams-become-multipliers"><strong>Platform Teams Become Multipliers</strong></h2>
<p>Many organizations interpret PaaS as buying a vendor product. That misses the bigger idea.</p>
<p>PaaS is fundamentally about creating reusable capabilities. Some organizations buy platforms. Others build internal platforms.</p>
<p>The principle remains the same.</p>
<p>A platform team creates systems once and allows everyone else to benefit. Instead of dozens of product teams independently solving operational problems, a dedicated group centralizes expertise and builds reusable solutions.</p>
<p>The effect becomes substantial. One deployment improvement accelerates every future release. One observability improvement strengthens every application. One security enhancement protects every team.</p>
<p>Platform teams create organizational leverage. Without this model, expertise stays fragmented. With it, expertise compounds.</p>
<h2 id="heading-easier-starts-create-more-innovation"><strong>Easier Starts Create More Innovation</strong></h2>
<p>Operational friction changes behaviour. When launching projects becomes expensive, organizations become cautious. Teams avoid experiments. Small ideas feel risky. Prototypes become difficult to justify.</p>
<p>Over time, innovation slows. Not because organizations lack ideas, but because starting became too expensive.</p>
<p>Teams running mature platforms understand this relationship. Reducing startup friction increases experimentation. Smaller projects become practical. Learning cycles become shorter.</p>
<p>New ideas appear more often because the cost of testing them falls dramatically. The easier it becomes to launch something, the more opportunities organizations create.</p>
<p>PaaS reduces startup friction. That reduction changes culture.</p>
<h2 id="heading-when-specialized-control-actually-matters"><strong>When Specialized Control Actually Matters</strong></h2>
<p>There are exceptions. Massive data platforms, highly specialized machine learning systems, and extremely customized environments may require lower-level infrastructure ownership.</p>
<p>Some workloads genuinely need deeper operational control. But these scenarios are exceptions, not defaults. Too many teams inherit infrastructure complexity designed for edge cases and treat it as standard practice.</p>
<p>Most production applications do not need custom orchestration layers. Most teams do not need to own Kubernetes. Most engineering groups do not need to spend weeks assembling infrastructure before shipping software.</p>
<p>The default assumption should be the opposite.</p>
<h2 id="heading-starting-from-zero-is-a-process-failure"><strong>Starting From Zero Is a Process Failure</strong></h2>
<p>Many organizations normalize unnecessary operational drag. Long setup cycles become accepted. Infrastructure duplication becomes routine. Cloud complexity becomes expected.</p>
<p>Eventually, teams stop questioning it. They assume this is simply how engineering works. It is not.</p>
<p>If launching a new application requires weeks of foundational setup before customer value appears, that is not an engineering discipline.</p>
<p>The goal was never to become an infrastructure company. It was to ship software.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Why Your “Simple Deploy” Turned Into a Week of Infrastructure Work ]]>
                </title>
                <description>
                    <![CDATA[ If you're running production workloads, this guide is for you. It's not about side projects, early-stage experiments, or a single-service app with low traffic. This is for teams shipping real systems. ]]>
                </description>
                <link>https://www.freecodecamp.org/news/why-your-simple-deploy-turned-into-a-week-of-infrastructure-work/</link>
                <guid isPermaLink="false">6a022071fca21b0d4b57374f</guid>
                
                    <category>
                        <![CDATA[ deployment ]]>
                    </category>
                
                    <category>
                        <![CDATA[ PaaS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ infrastructure ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Manish Shivanandhan ]]>
                </dc:creator>
                <pubDate>Mon, 11 May 2026 18:31:13 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/14f4724b-fb2b-4454-a3dd-3d250e126f50.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you're running production workloads, this guide is for you.</p>
<p>It's not about side projects, early-stage experiments, or a single-service app with low traffic.</p>
<p>This is for teams shipping real systems. Systems with users, uptime expectations, and release pressure.</p>
<p>Because at that stage, your deploy process is no longer a convenience. It's part of your product.</p>
<p>And right now, for most teams, it's the weakest part.</p>
<p>In this article, we'll look at why deployment complexity keeps growing as systems scale, how modern tooling unintentionally pushes teams into platform engineering work, and why many production teams are rethinking the infrastructure they manage themselves.</p>
<p>We'll also look at where Platform as a Service (PaaS) fits into this shift, what trade-offs it introduces, and when adopting one actually makes sense.</p>
<h3 id="heading-what-well-cover">What We'll Cover:</h3>
<ul>
<li><p><a href="#heading-the-promise-you-were-sold">The Promise You Were Sold</a></p>
</li>
<li><p><a href="#heading-the-hidden-contract-you-are-already-operating-under">The Hidden Contract You Are Already Operating Under</a></p>
</li>
<li><p><a href="#heading-you-are-already-acting-like-a-platform-team">You Are Already Acting Like a Platform Team</a></p>
</li>
<li><p><a href="#heading-the-cost-is-not-complexity-it-is-time">The Cost Is Not Complexity. It Is Time</a></p>
</li>
<li><p><a href="#heading-why-it-works-on-my-machine-still-exists">Why “It Works on My Machine” Still Exists</a></p>
</li>
<li><p><a href="#heading-fragmentation-is-the-root-problem">Fragmentation Is the Root Problem</a></p>
</li>
<li><p><a href="#heading-this-model-breaks-as-you-scale">This Model Breaks as You Scale</a></p>
</li>
<li><p><a href="#heading-the-shift-toward-platforms">The Shift Toward Platforms</a></p>
</li>
<li><p><a href="#heading-what-you-stop-paying-for">What You Stop Paying For</a></p>
</li>
<li><p><a href="#heading-from-infrastructure-work-back-to-product-work">From Infrastructure Work Back to Product Work</a></p>
</li>
<li><p><a href="#heading-collapsing-the-stack">Collapsing the Stack</a></p>
</li>
<li><p><a href="#heading-the-trade-off-you-are-actually-making">The Trade-Off You Are Actually Making</a></p>
</li>
<li><p><a href="#heading-when-this-becomes-urgent">When This Becomes Urgent</a></p>
</li>
<li><p><a href="#heading-what-a-simple-deploy-actually-means">What a “Simple Deploy” Actually Means</a></p>
</li>
<li><p><a href="#heading-closing-thought">Closing Thought</a></p>
</li>
</ul>
<h2 id="heading-the-promise-you-were-sold">The Promise You Were Sold</h2>
<p>Every modern stack makes the same promise: Shipping is easy. Deploying is automated. Infrastructure is abstracted away. Push your code. Watch it go live.</p>
<p>That promise works , until it doesn’t.</p>
<p>And when it breaks, it doesn't fail gracefully. It expands.</p>
<p>A “simple deploy” turns into a multi-day investigation across systems you never intended to own.</p>
<p>Not because your team is careless. Because the model itself assumes you'll take on more responsibility than it admits.</p>
<h2 id="heading-the-hidden-contract-you-are-already-operating-under">The Hidden Contract You Are Already Operating Under</h2>
<p>When you deploy today, you're not just shipping code. You're agreeing to run a <a href="https://www.splunk.com/en_us/blog/learn/distributed-systems.html">distributed system</a> of tools.</p>
<p>You own the build pipeline, the container lifecycle, the runtime configuration, the network rules, the secrets layer, the scaling logic, and the observability stack.</p>
<p>Each of these is presented as a separate concern. In reality, they're tightly coupled.</p>
<p>And you're the only layer holding them together. That's the hidden contract.</p>
<h2 id="heading-you-are-already-acting-like-a-platform-team">You Are Already Acting Like a Platform Team</h2>
<p>If your deploy process involves CI pipelines, container registries, cloud services, environment variables, and monitoring tools, you're not just an application team anymore. You're running a platform.</p>
<p>You're defining how code moves from commit to production. You're deciding how failures are handled. And you're shaping how services communicate.</p>
<p>That's platform engineering work.</p>
<p>The issue isn't that this work exists. The issue is that most teams take it on unintentionally, without the structure, tooling, or dedicated ownership a real platform team would require.</p>
<h2 id="heading-the-cost-is-not-complexity-it-is-time">The Cost Is Not Complexity. It Is Time</h2>
<p>It's easy to describe this problem as “complexity.” But that undersells it.</p>
<p>The real cost shows up in how your team spends its time.</p>
<p>Deploys that should take minutes stretch into hours. Then days. Engineers context-switch from product work into debugging <a href="https://www.youtube.com/watch?v=dhiGWtnk4Rk">CI caches</a>, fixing misconfigured secrets, or tracing network failures across services.</p>
<p>Releases slow down. Not because your team can't build features, but because shipping them becomes unpredictable.</p>
<p>Onboarding gets harder. New engineers don't just learn the codebase. They have to learn your deployment system.</p>
<p>None of this appears on a roadmap. But it directly impacts how fast you can move.</p>
<h2 id="heading-why-it-works-on-my-machine-still-exists">Why “It Works on My Machine” Still Exists</h2>
<p>We were supposed to have solved this: Containers. Infrastructure as code. Reproducible builds.</p>
<p>Yet the gap between local and production still shows up at the worst possible moment.</p>
<p>Because the problem was never just environment parity. It's system parity.</p>
<p>Your local setup doesn't include the same limits, permissions, network paths, or scaling behavior as production.</p>
<p>Those differences only surface when everything is wired together. Which means they surface during deploys.</p>
<h2 id="heading-fragmentation-is-the-root-problem">Fragmentation Is the Root Problem</h2>
<p>Modern tooling didn't remove infrastructure complexity. It redistributed it.</p>
<p>Instead of managing servers, you manage integrations between services. Instead of a single failure domain, you have many.</p>
<p>A deploy can fail because of a CI issue, a registry timeout, a secret misconfiguration, a networking rule, or a scaling limit.</p>
<p>Each lives in a different system. Each requires different context.</p>
<p>Individually, these tools are well-designed. Collectively, they form a system that's hard to reason about under pressure.</p>
<h2 id="heading-this-model-breaks-as-you-scale">This Model Breaks as You Scale</h2>
<p>This only works while your system is small. But production systems don't stay small.</p>
<p>More services mean more pipelines. More configurations. More failure points.</p>
<p>Over time, the effort required to maintain your deployment system grows faster than the product itself.</p>
<p>That is the inflection point: where engineering time shifts away from building features and toward maintaining the machinery that ships them.</p>
<p>If you're already feeling that shift, it's not temporary. It's structural.</p>
<p>At some point, there's a question that becomes hard to ignore: Why are you still managing this yourself?</p>
<p>Not because you can't. But because it's no longer clear that you should.</p>
<h2 id="heading-the-shift-toward-platforms">The Shift Toward Platforms</h2>
<p>This is where <a href="https://www.freecodecamp.org/news/from-metrics-to-meaning-how-paas-helps-developers-understand-production/">Platform as a Service</a> changes the model. Not by adding more tools, but by taking ownership of the system those tools create.</p>
<p>A PaaS defines a path from code to production. That path is opinionated, constrained, and consistent.</p>
<p>Those constraints aren't limitations. They're what remove entire categories of failure.</p>
<p>Instead of assembling a deployment pipeline, you adopt one.</p>
<h2 id="heading-what-you-stop-paying-for">What You Stop Paying For</h2>
<p>Moving to a PaaS is often framed as convenience. For production teams, it's closer to cost removal.</p>
<p>You stop spending time deciding how builds run, how services are exposed, how scaling is configured, and how logs are collected.</p>
<p>You stop debugging the integration points between those decisions. You trade flexibility for predictability.</p>
<p>And for most teams, predictability is the constraint that actually matters.</p>
<h2 id="heading-from-infrastructure-work-back-to-product-work">From Infrastructure Work Back to Product Work</h2>
<p>The biggest change isn't in your architecture. It's in your allocation of engineering effort.</p>
<p>Time spent debugging deploys shifts back to building features. Time spent maintaining pipelines shifts to improving the product.</p>
<p>Deploys become routine again. Not because they're simpler in theory, but because the system around them is controlled.</p>
<h2 id="heading-collapsing-the-stack">Collapsing the Stack</h2>
<p>The advantage of a PaaS isn't abstraction. It's consolidation.</p>
<p>Build, deploy, runtime, and observability are integrated into a single system.</p>
<p>There are fewer layers to coordinate. Fewer places to look when something fails. And fewer decisions to get wrong.</p>
<p>Platforms like <a href="https://sevalla.com/">Sevalla</a>, Railway, and Render are pushing this further by tightening the loop between code and production, reducing both the number of systems involved and the surface area developers need to understand.</p>
<p>The goal is operational clarity.</p>
<h2 id="heading-the-trade-off-you-are-actually-making">The Trade-Off You Are Actually Making</h2>
<p>The common objection is control. And it's valid. You give up the ability to customize every layer of your infrastructure.</p>
<p>But in practice, most teams aren't using that control to create differentiation. They're using it to keep a fragile system running, and it’s what keeps teams stuck maintaining systems they shouldn’t own.</p>
<p>Every custom configuration adds another failure point. Another dependency. Another thing to maintain under pressure.</p>
<p>The trade-off isn't control versus convenience. It's control versus reliability.</p>
<h3 id="heading-when-this-becomes-urgent">When This Becomes Urgent</h3>
<p>You don't need a major outage to justify a change. The signals show up earlier.</p>
<p>Deploys feel unpredictable. Releases slow down. Engineers spend more time on pipelines than product logic. Onboarding takes longer than it should.</p>
<p>These aren't isolated issues. They are indicators that your current model isn't scaling with your system.</p>
<h2 id="heading-when-managing-infra-still-makes-sense">When Managing Infra Still Makes Sense</h2>
<p>A PaaS may not right for every team.</p>
<p>If your app is still small, deployments are smooth, and your team isn't spending much time on infrastructure, you may not need a PaaS yet.</p>
<p>Some large companies also choose to build and manage their own platforms. For them, infrastructure is an important part of the business, so the extra work is worth it.</p>
<p>The important thing is making that choice on purpose.</p>
<p>Managing infrastructure is not always a bad thing. The real problem starts when app teams slowly take on platform work without enough people, clear ownership, or the right experience to handle it well.</p>
<h3 id="heading-what-a-simple-deploy-actually-means">What a “Simple Deploy” Actually Means</h3>
<p>A simple deploy isn't one that feels easy when everything works. It's one that continues to work as your system grows.</p>
<p>It's predictable. Failures are rare. When they happen, they're easy to diagnose.</p>
<p>And most importantly, it doesn't require your engineers to think about infrastructure to ship code.</p>
<p>That outcome isn't achieved by adding more tools. It's achieved by reducing the system you have to manage.</p>
<h2 id="heading-closing-thought">Closing Thought</h2>
<p>Your deploy didn't turn into a week of infrastructure work because you missed something. It turned into that because you're operating a model that expects you to.</p>
<p>You can continue investing in that model. Or you can adopt one where deploying is a solved problem.</p>
<p>For production teams, that's no longer a philosophical choice. It's an operational one.</p>
<p><em>Join my</em> <a href="https://applyaito.substack.com/"><em><strong>Applied AI newsletter</strong></em></a> <em>to learn how to build and ship real AI systems. Practical projects, production-ready code, and direct Q&amp;A. You can also</em> <a href="https://www.linkedin.com/in/manishmshiva/"><em><strong>connect with me on</strong></em> <em><strong>LinkedIn</strong></em></a><em><strong>.</strong></em></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ From Metrics to Meaning: How PaaS Helps Developers Understand Production ]]>
                </title>
                <description>
                    <![CDATA[ Modern production systems generate more data than most developers can realistically process. Every request emits logs. Every service exports metrics. Every dependency introduces another layer of signa ]]>
                </description>
                <link>https://www.freecodecamp.org/news/from-metrics-to-meaning-how-paas-helps-developers-understand-production/</link>
                <guid isPermaLink="false">69ea4e46904b91543899894d</guid>
                
                    <category>
                        <![CDATA[ PaaS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ infrastructure ]]>
                    </category>
                
                    <category>
                        <![CDATA[ metrics ]]>
                    </category>
                
                    <category>
                        <![CDATA[ production ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Manish Shivanandhan ]]>
                </dc:creator>
                <pubDate>Thu, 23 Apr 2026 16:52:22 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/e30cdb93-e709-4f28-89fc-ba004735e400.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Modern production systems generate more data than most developers can realistically process.</p>
<p>Every request emits logs. Every service exports metrics. Every dependency introduces another layer of signals.</p>
<p>In theory, this should make systems easier to understand. In practice, it does the opposite.</p>
<p>Dashboards become dense, alerts become noisy, and when something breaks, the same questions still come up: What's actually wrong? Who's affected? Where do you even start?</p>
<p>The problem isn't observability. It's interpretation.</p>
<p>Most teams aren't short on metrics. They're short on meaning.</p>
<p>And that gap exists because developers are often forced to reason about infrastructure when they should be focused on application behaviour.</p>
<p>Metrics exist to describe systems, but without the right level of abstraction, they become another layer of complexity.</p>
<p>This is where modern PaaS platforms change the equation. They don't remove metrics. Instead, they turn them into signals that developers can actually use.</p>
<p>This article breaks down five metrics that consistently matter in production systems. More importantly, it shows how a PaaS helps translate these metrics into something actionable, without requiring developers to act as infrastructure operators.</p>
<p>I’ll be using the <a href="https://sevalla.com/">Sevalla</a> dashboard to explain these metrics, but other platforms like Railway and Render will have similar metrics.</p>
<h3 id="heading-what-well-cover">What We'll Cover:</h3>
<ul>
<li><p><a href="#heading-what-a-paas-actually-does">What a PaaS Actually Does</a></p>
</li>
<li><p><a href="#heading-latency-becomes-a-clear-performance-signal">Latency Becomes a Clear Performance Signal</a></p>
</li>
<li><p><a href="#heading-error-rate-becomes-a-reliable-indicator-of-failure">Error Rate Becomes a Reliable Indicator of Failure</a></p>
</li>
<li><p><a href="#heading-throughput-becomes-context-instead-of-a-problem">Throughput Becomes Context Instead of a Problem</a></p>
</li>
<li><p><a href="#heading-resource-utilisation-moves-out-of-the-critical-path">Resource Utilisation Moves Out of the Critical Path</a></p>
</li>
<li><p><a href="#heading-instance-health-becomes-invisible-by-design">Instance Health Becomes Invisible by Design</a></p>
</li>
<li><p><a href="#heading-from-metrics-to-meaning">From Metrics to Meaning</a></p>
</li>
<li><p><a href="#heading-why-this-matters-for-developers">Why This Matters for Developers</a></p>
</li>
<li><p><a href="#heading-the-real-advantage-is-clarity">The Real Advantage Is Clarity</a></p>
</li>
</ul>
<h2 id="heading-what-a-paas-actually-does">What a PaaS Actually Does</h2>
<p>A Platform as a Service (PaaS) is an abstraction layer over infrastructure that handles deployment, scaling, networking, and runtime management for you.</p>
<p>Instead of provisioning servers, configuring load balancers, and setting up autoscaling rules, you deploy your application and the platform takes care of how it runs in production.</p>
<p>Platforms like Sevalla, Railway, and Render operate on this model. The key shift is responsibility.</p>
<p>In a traditional setup, developers are responsible for both application behaviour and infrastructure behaviour. If latency spikes or errors increase, you have to determine whether the issue is in your code, your scaling rules, or the underlying system.</p>
<p>A PaaS moves most of that infrastructure responsibility into the platform.</p>
<p>You still get access to metrics, but many of the variables behind those metrics –instance lifecycle, scaling decisions, resource allocation –&nbsp;are handled automatically.</p>
<p>This changes how you interpret what you see.</p>
<p>Metrics stop being signals that require cross-layer investigation, and start becoming signals that map more directly to application behaviour.</p>
<p>Now let's see what can happen if your team switches to using a PaaS.</p>
<h2 id="heading-latency-becomes-a-clear-performance-signal"><strong>Latency Becomes a Clear Performance Signal</strong></h2>
<img src="https://cdn.hashnode.com/uploads/covers/66c6d8f04fa7fe6a6e337edd/4b0ed69b-d122-497c-9cd4-ad8d7b29584a.webp" alt="Latency graph" style="display:block;margin:0 auto" width="1535" height="410" loading="lazy">

<p>Latency is the most direct representation of user experience. It tells you how long your system takes to respond.</p>
<p>When latency increases, users feel it immediately. Pages slow down. APIs become unreliable. Even small delays impact engagement.</p>
<p>Most developers know to look at percentiles like p95 or p99 instead of averages. The slowest requests are what define perceived performance.</p>
<p>But in many environments, understanding latency isn't straightforward.</p>
<p>A spike could come from inefficient code. Or from cold starts. Or from scaling delays. Or from network routing issues. Developers are forced to investigate layers they didn't build.</p>
<p>This is where a PaaS changes the role of latency.</p>
<img src="https://cdn.hashnode.com/uploads/covers/66c6d8f04fa7fe6a6e337edd/d22aac3e-5a50-4f6c-baa9-63afc388da54.webp" alt="Speed metrics" style="display:block;margin:0 auto" width="1536" height="562" loading="lazy">

<p>Instead of being a starting point for infrastructure debugging, latency becomes a clean signal of application performance. Scaling, routing, and resource allocation are handled by the platform. What remains is a clearer relationship between code and outcome.</p>
<p>When latency increases, developers can focus on what they actually control: queries, logic, and dependencies.</p>
<p>The metric stays the same. The meaning becomes clearer.</p>
<h2 id="heading-error-rate-becomes-a-reliable-indicator-of-failure"><strong>Error Rate Becomes a Reliable Indicator of Failure</strong></h2>
<img src="https://cdn.hashnode.com/uploads/covers/66c6d8f04fa7fe6a6e337edd/664b6eab-f43d-4aec-a412-825d0c7c060b.webp" alt="Error rate graph" style="display:block;margin:0 auto" width="1226" height="288" loading="lazy">

<p>Error rate answers a simple question. Is the system working or not?</p>
<p>It's usually measured as the percentage of requests that fail due to server-side issues. These are failures users can't recover from. A broken checkout flow or a failed API call directly impacts trust.</p>
<p>In theory, error rate should be one of the easiest metrics to act on. In practice, it rarely is.</p>
<p>Errors can come from application bugs, but also from timeouts, resource limits, failed deployments, or unstable instances. Developers end up correlating errors with infrastructure events just to understand what happened.</p>
<p>This slows everything down.</p>
<p>A PaaS reduces this ambiguity.</p>
<p>Failures caused by scaling, instance crashes, or transient infrastructure issues are handled at the platform level. Retries, isolation, and recovery mechanisms are built in.</p>
<p>What remains is a tighter link between error rate and application correctness.</p>
<p>When the error rate increases, it's far more likely to be something in the code or a dependency, not an invisible infrastructure issue.</p>
<p>This shifts the error rate from a noisy metric into a reliable signal.</p>
<h2 id="heading-throughput-becomes-context-instead-of-a-problem"><strong>Throughput Becomes Context Instead of a Problem</strong></h2>
<img src="https://cdn.hashnode.com/uploads/covers/66c6d8f04fa7fe6a6e337edd/045bbee4-8d29-4a9f-a6e7-e235e08bc920.webp" alt="Throughput graph" style="display:block;margin:0 auto" width="1533" height="398" loading="lazy">

<p>Throughput measures how many requests your system handles over time.</p>
<p>It provides context for everything else. Latency and error rate only make sense when you know how much traffic the system is handling.</p>
<p>A spike in latency during high traffic is expected. The same spike during low traffic is a warning sign.</p>
<p>But in many systems, throughput introduces operational complexity. Traffic changes require scaling decisions. Teams define autoscaling rules, tune thresholds, and try to predict demand. When things go wrong, they revisit those decisions.</p>
<p>Developers end up thinking about capacity instead of behaviour.</p>
<p>A PaaS shifts this responsibility. Scaling is automatic. Traffic spikes are absorbed by the platform. Developers don't need to decide how many instances should be running or when to scale.</p>
<p>Throughput becomes what it should be: context.</p>
<p>It helps explain what's happening, without forcing developers to manage how the system adapts.</p>
<h2 id="heading-resource-utilisation-moves-out-of-the-critical-path"><strong>Resource Utilisation Moves Out of the Critical Path</strong></h2>
<img src="https://cdn.hashnode.com/uploads/covers/66c6d8f04fa7fe6a6e337edd/3636488f-7648-4db8-ae00-1c8374ca46ba.webp" alt="Sytem utilization" style="display:block;margin:0 auto" width="1534" height="517" loading="lazy">

<p>Resource utilization measures how much CPU, memory, and I/O your system consumes.</p>
<p>Traditionally, this has been central to operating systems. High CPU or memory usage signals potential issues. Teams monitor these metrics to avoid failures and plan scaling.</p>
<p>But for most developers, resource utilization isn't where value is created.</p>
<p>Yet in many environments, developers are still responsible for interpreting these signals. They tune memory limits, investigate CPU spikes, and try to optimise resource usage to keep systems stable.</p>
<p>This is operational work.</p>
<p>A PaaS changes the role of these metrics.</p>
<p>Resource management is handled by the platform. Allocation, scaling, and isolation happen automatically. Developers don't need to constantly watch CPU graphs or memory charts to keep the system running.</p>
<p>These metrics still exist, but they move into the background.</p>
<p>They become diagnostic tools rather than primary signals.</p>
<p>Developers can focus on performance at the application level, instead of managing how infrastructure behaves under load.</p>
<h2 id="heading-instance-health-becomes-invisible-by-design"><strong>Instance Health Becomes Invisible by Design</strong></h2>
<img src="https://cdn.hashnode.com/uploads/covers/66c6d8f04fa7fe6a6e337edd/fd4a755d-c90c-45fd-843e-9be5e5f85caf.webp" alt="Instance health" style="display:block;margin:0 auto" width="1544" height="417" loading="lazy">

<p>Instance health tracks restarts, crashes, and lifecycle events.</p>
<p>In many systems, this is a critical metric. Frequent restarts indicate instability. Memory leaks, crashes, or resource exhaustion often show up here first.</p>
<p>Teams monitor instance health to catch issues early and prevent cascading failures.</p>
<p>But this also reveals something important: developers are aware of, and responsible for, the lifecycle of infrastructure. They track restarts, investigate crashes, and try to stabilise the system manually.</p>
<p>A PaaS removes this responsibility.</p>
<p>Unhealthy instances are restarted automatically. Load is redistributed. Capacity is maintained without manual intervention.</p>
<p>Instance health doesn't disappear, but it no longer requires constant attention. It becomes part of the platform’s internal behaviour, not something developers need to actively manage.</p>
<h2 id="heading-from-metrics-to-meaning"><strong>From Metrics to Meaning</strong></h2>
<p>These five metrics haven't changed.</p>
<p>Latency still reflects performance. Error rate still reflects correctness. Throughput still reflects demand. Resource utilization still reflects efficiency. Instance health still reflects stability.</p>
<p>What changes is how much work it takes to interpret them.</p>
<p>In lower-level environments, developers have to connect these signals themselves. A latency spike leads to checking throughput, then resource usage, then instance behaviour. Each step requires context, assumptions, and time.</p>
<p>This is where complexity accumulates.</p>
<p>A PaaS reduces that gap.</p>
<p>It handles scaling, recovery, and resource management so that metrics map more directly to application behaviour. The signals become easier to interpret because fewer variables are exposed.</p>
<p>Instead of asking multiple questions across layers, developers can move more directly from symptom to cause.</p>
<h2 id="heading-why-this-matters-for-developers"><strong>Why This Matters for Developers</strong></h2>
<p>Most developers don't want to manage infrastructure. They want to build features, ship improvements, and respond to user needs.</p>
<p>But as systems grow, operational responsibility expands. Monitoring becomes more complex. Debugging requires more context. A significant portion of time shifts from building to maintaining.</p>
<p>Metrics are part of this shift.</p>
<p>They're necessary, but they also reflect how much of the system you're responsible for understanding.</p>
<p>A PaaS doesn't eliminate metrics. It reduces the effort required to make sense of them.</p>
<p>It ensures that when something changes in production, the signals developers see are closer to the reality they care about: application behaviour. User experience. System correctness.</p>
<h2 id="heading-the-real-advantage-is-clarity"><strong>The Real Advantage Is Clarity</strong></h2>
<p>The goal is not to have fewer metrics.</p>
<p>It's to have metrics that mean something without requiring deep infrastructure reasoning.</p>
<p>These five metrics form a complete picture of system health. But their real value depends on how directly they map to what developers control.</p>
<p>The more layers you have to think about, the harder mapping becomes.</p>
<p>A good PaaS removes those layers. It turns metrics from raw data into usable signals.</p>
<p>And that shift from metrics to meaning is what allows developers to understand production systems without being buried under them.</p>
<p><em>Join my</em> <a href="https://applyaito.substack.com/"><em><strong>Applied AI newsletter</strong></em></a> <em>to learn how to build and ship real AI systems. Practical projects, production-ready code, and direct Q&amp;A. You can also</em> <a href="https://www.linkedin.com/in/manishmshiva/"><em><strong>connect with me on</strong></em> <em><strong>LinkedIn</strong></em></a><em><strong>.</strong></em></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ VPS vs PaaS: How to Choose a Hosting Solution ]]>
                </title>
                <description>
                    <![CDATA[ If you’ve ever stared at a dozen hosting plans, not sure which one to choose, you’re not alone. Hosting isn’t one-size-fits-all, and knowing the difference between a VPS (Virtual Private Server) and a PaaS (Platform as a Service) can help you pick so... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/vps-vs-paas-how-to-choose-a-hosting-solution/</link>
                <guid isPermaLink="false">687fca00b102539f23984a03</guid>
                
                    <category>
                        <![CDATA[ hosting ]]>
                    </category>
                
                    <category>
                        <![CDATA[ server ]]>
                    </category>
                
                    <category>
                        <![CDATA[ PaaS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ vps ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Cloud Computing ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Manish Shivanandhan ]]>
                </dc:creator>
                <pubDate>Tue, 22 Jul 2025 17:27:28 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1753205132683/65ed718f-a68e-4e31-8db3-cf9265e50817.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you’ve ever stared at a dozen hosting plans, not sure which one to choose, you’re not alone. Hosting isn’t one-size-fits-all, and knowing the difference between a VPS (Virtual Private Server) and a PaaS (Platform as a Service) can help you pick something that works for your project.</p>
<p>Let’s break them down clearly. We’ll go through VPS and PaaS in detail in terms of scaling, pricing, control, and so on. Each handles hosting very differently, and by the end of this guide, you’ll know exactly which solution fits your workflow better.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-a-vps">What is a VPS?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-a-paas">What is a PaaS?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-control-and-customization">Control and Customisation</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-setup-and-deployment">Setup and Deployment</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-scaling">Scaling</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-maintenance-and-updates">Maintenance and Updates</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-performance">Performance</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-security">Security</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-pricing">Pricing</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-when-to-use-each">When to Use Each</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-summary">Summary</a></p>
</li>
</ul>
<h2 id="heading-what-is-a-vps">What is a VPS?</h2>
<p>VPS stands for <a target="_blank" href="https://cloud.google.com/learn/what-is-a-virtual-private-server">Virtual Private Server</a>. Think of it as your own slice of a physical server.</p>
<p>Unlike shared hosting, where you compete for resources, a VPS gives you isolated computing power, dedicated RAM, CPU, and storage that’s all yours.</p>
<p>It acts like a mini datacenter. You gain root access, allowing you to install any OS (such as Ubuntu or CentOS), run custom applications, set up cron jobs, configure firewall rules, and essentially shape the environment as you see fit. It’s flexible, affordable, and powerful, and ideal for developers who want control without the complexity of managing bare-metal hardware.</p>
<h2 id="heading-what-is-a-paas">What is a PaaS?</h2>
<p>PaaS stands for <a target="_blank" href="https://azure.microsoft.com/en-us/resources/cloud-computing-dictionary/what-is-paas">Platform as a Service</a>. It’s a cloud-based environment that lets you build, deploy, and scale applications without worrying about infrastructure.</p>
<p>Instead of provisioning servers or managing software stacks, you simply write your code, connect your Git repository, and hit deploy. The platform takes care of everything from building your app, routing traffic, provisioning SSL, scaling services, and monitoring health. It’s DevOps on autopilot.</p>
<p>PaaS solutions are built for speed and simplicity. They support modern languages and frameworks out of the box and offer smart features like auto-scaling, built-in CI/CD, and usage-based pricing.</p>
<p>Now let’s look at the main differences between the two options so you can decide which is best for your use case.</p>
<h2 id="heading-control-and-customisation"><strong>Control and Customisation</strong></h2>
<p>A VPS gives you full control. It’s your server, your rules. </p>
<p>You get root access, pick your OS, install whatever software you need, and tweak system settings to your liking. VPS solutions makes this easy by letting you deploy clean server images quickly like Ubuntu, Debian, Redhat or whatever suits you. Then it’s all in your hands.</p>
<p>PaaS, on the other hand, limits a bit of that flexibility in exchange for convenience. PaaS abstracts the system layer away completely and often comes with support which is handy if you need help. </p>
<p>You write code, push to a Git repo, and it takes care of the rest. It supports popular languages and frameworks, but if you need a very specific runtime or library, you might hit a wall.</p>
<p>If you like being in control, VPS wins here. If you’d rather avoid infrastructure altogether, PaaS is your solution. </p>
<h2 id="heading-setup-and-deployment"><strong>Setup and Deployment</strong></h2>
<p>Getting a VPS up and running takes more effort. You’ll start by provisioning a server, then SSH in to install packages, configure firewalls, set up your web server, and deploy your code manually or via tools like <a target="_blank" href="https://www.freecodecamp.org/news/the-docker-handbook/">Docker</a>.</p>
<p>With PaaS, setup is nearly instant. You connect your GitHub or GitLab account, select your repo, and click deploy. It handles building, routing, SSL certificates, and launching the app, all within minutes. No SSH, no terminal commands, no surprises.</p>
<p>So if you want fast and repeatable deployments, PaaS is the smoother ride. If you’re okay spending more time upfront to craft your ideal setup, VPS gives you the flexibility.</p>
<h2 id="heading-scaling"><strong>Scaling</strong></h2>
<p>Scaling is one of the biggest advantages of PaaS. When your app traffic increases, it can spin up more containers or instances automatically. </p>
<p>You don’t have to predict resource needs ahead of time. Your app scales with demand and scales back down to save money when things quiet down.</p>
<p>With a VPS, scaling is more manual. You have to monitor usage and upgrade your server or configure load balancers yourself. Some developers enjoy this level of control, especially when optimising resource use. But it can be a headache during unexpected traffic spikes.</p>
<p>If your app is likely to grow or experience unpredictable load, PaaS gives you peace of mind. If your traffic is steady and predictable, VPS can handle it just fine, especially if you’re comfortable managing the growth yourself.</p>
<h2 id="heading-maintenance-and-updates"><strong>Maintenance and Updates</strong></h2>
<p>VPS means you’re in charge of everything under the hood. That includes system updates, security patches, disk usage, and log rotation. You also need to manage backups, monitoring, and anything else that keeps your app healthy and online.</p>
<p>PaaS removes that burden. The platform takes care of OS-level updates, security patches, and even restarts or auto-heals when something goes wrong. You get built-in monitoring and automatic backups, and logs are available right from the dashboard.</p>
<p>If maintenance isn’t your strong suit, or just not how you want to spend your valuable time, PaaS clearly comes out ahead.</p>
<h2 id="heading-performance"><strong>Performance</strong></h2>
<p>With a VPS, you get guaranteed resources. They offers dedicated CPU cores and RAM that only your apps use. You can fine-tune performance at every level, from <a target="_blank" href="https://www.freecodecamp.org/news/the-nginx-handbook/">Nginx</a> config files to memory usage. But I would recommend that you read the provider’s fine print and service terms as dedicated resources are not always fully dedicated.</p>
<p>PaaS solutions often run apps in shared or containerised environments. They manage performance for you and isolate workloads, but you might not have the same raw consistency as with a dedicated VPS, especially under heavy compute loads.</p>
<p>For apps that demand consistent high performance, like an online streaming service, a VPS is often the better choice. For most typical web apps, PaaS delivers more than enough speed and stability.</p>
<h2 id="heading-security"><strong>Security</strong></h2>
<p>In a VPS, security is your responsibility. That includes setting up firewalls, securing SSH access, managing user roles, and keeping the OS up to date. VPS gives you the tools, but it’s up to you to use them correctly.</p>
<p>PaaS handles most security concerns automatically, including <a target="_blank" href="https://www.freecodecamp.org/news/protect-against-ddos-attacks/">DDoS protection</a>. It provides HTTPS out of the box, isolates apps from each other, and keeps the platform patched and hardened. While you’re still responsible for securing your app code, you don’t have to worry about the infrastructure.</p>
<p>If security isn’t your strong point, or you want to reduce risk, PaaS adds a safety net. For experienced sysadmins, VPS offers the flexibility to build your own defenses.</p>
<h2 id="heading-pricing"><strong>Pricing</strong></h2>
<p>VPS pricing may appear more affordable at first glance. A VPS server with 4 GB RAM and 80 GB SSD might only set you back $10-15 per month. But that price is fixed, whether your app is serving ten users or ten thousand. And when you outgrow that plan, scaling means resizing the server or juggling additional machines.</p>
<p>PaaS platforms take a different approach. Instead of paying for fixed resources you may or may not use, you pay for what you actually consume. If your app gets minimal traffic, your costs stay low. But if usage spikes, PaaS scales your resources to match, without downtime or manual effort. You’re billed based on activity, not guesswork.</p>
<p>This makes PaaS a better long-term deal for most modern apps. You’re not locked into static hardware. You don’t have to overpay just to be “safe.” And as your app scales, your infrastructure scales with it automatically. </p>
<p>But keep in mind that since PaaS platforms scale automatically based on demand, a sudden spike in traffic can lead to unexpectedly high costs. To avoid surprise bills, make sure to set up pricing alerts and usage thresholds. Most PaaS providers offer these features to help you stay in control of your budget.</p>
<h2 id="heading-when-to-use-each"><strong>When to Use Each</strong></h2>
<p>Use a VPS if you need complete control, want to host multiple apps on one server, or have special requirements around software, performance, or system-level configuration. </p>
<p><a target="_blank" href="https://www.hetzner.com/">Hetzner</a> is a great choice when you want a solid server at a good price and are comfortable managing it yourself. It offers powerful virtual servers with full root access, making it a favorite among developers who want total control. If you’re comfortable managing your own infrastructure, Hetzner gives you the tools and flexibility to build exactly what you need.</p>
<p>Choose PaaS if you want to move fast, avoid infrastructure headaches, and focus purely on coding. PaaS lets you deploy and scale apps with minimal effort, which makes it ideal for teams that want to spend more time building and growing their business than managing. </p>
<p><a target="_blank" href="https://sevalla.com/">Sevalla</a> is a modern PaaS built for speed and simplicity. It handles everything from deployments to scaling, so you can focus entirely on writing code. With smart usage-based pricing and built-in automation, Sevalla is ideal for developers who want to move fast without managing servers or infrastructure.</p>
<h2 id="heading-summary"><strong>Summary</strong></h2>
<p>There’s no one-size-fits-all answer when choosing between VPS and PaaS. It depends on your priorities, whether you care more about control or convenience, price or speed, flexibility or simplicity.</p>
<p>A VPS gives you a clean slate and full power under the hood. It’s ideal for experienced developers and sysadmins who want to build their environment from the ground up.</p>
<p>A PaaS offering gives you the tools to deploy fast, scale effortlessly, and skip the DevOps. It’s perfect if you’d rather write code than manage servers.</p>
<p>Hope you enjoyed this article. <a target="_blank" href="https://linkedin.com/in/manishmshiva">Connect with me</a> on LinkedIn or <a target="_blank" href="https://manishshivanandhan.com/">visit my website</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build Your Own Heroku with Dokku ]]>
                </title>
                <description>
                    <![CDATA[ By Nuno Bispo Heroku is a well-known PaaS widely used by developers. And as a fun and useful project, you can easily make your own Heroku-like PaaS with Dokku. What is Heroku? Heroku is a platform as a service (PaaS) company founded in 2007. The pla... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-your-on-heroku-with-dokku/</link>
                <guid isPermaLink="false">66d4608a733861e3a22a734b</guid>
                
                    <category>
                        <![CDATA[ Cloud Computing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Heroku ]]>
                    </category>
                
                    <category>
                        <![CDATA[ PaaS ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Fri, 04 Feb 2022 19:01:06 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/02/dokku.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Nuno Bispo</p>
<p>Heroku is a well-known PaaS widely used by developers. And as a fun and useful project, you can easily make your own Heroku-like PaaS with Dokku.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/dokku-logo-with-name5-1.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-what-is-heroku">What is Heroku?</h2>
<p>Heroku is a platform as a service (PaaS) company founded in 2007. The platform runs on AWS, and its ephemeral storage system is called "Dyno".</p>
<p>Heroku is one of the most used PaaS by developers and for a good reason– it is easy to use, well documented, and supports several programming languages.</p>
<p>But what if you could deploy your own Heroku-like platform, including a CI/CD pipeline, database connections, HTTPS connections, and much more with one simple-to-use application?</p>
<p>Well, that is what Dokku provides and more. Let's take a look.</p>
<h2 id="heading-what-is-a-paas">What is a PaaS?</h2>
<p>Platform-as-a-Service (PaaS) is a software architecture style that provides an easy-to-use abstraction layer for deploying your application's code and managing it.</p>
<p>This allows you to focus on writing business logic rather than worrying about the platform itself.</p>
<p>PaaS providers usually provide their own database service as well as other related services, which can greatly simplify common development tasks.</p>
<p>The great advantage of PaaS is that the application developer doesn't need to perform any system administration work. Instead, you can just upload your code and configuration settings to a central server platform.</p>
<p>The service then takes care of deploying the code, scaling it as needed, backing up data, handling hosting and uptime concerns, and so on.</p>
<h2 id="heading-what-is-dokku">What is Dokku?</h2>
<p>Dokku is a hosted Platform as a Service that enables developers to deploy their applications with ease.</p>
<p>From their website:</p>
<blockquote>
<p>The smallest PaaS implementation you've ever seen</p>
</blockquote>
<p>Dokku is based on Docker and uses Heroku's build-packs to compile and package your applications.</p>
<p>One of the best things about Dokku is that it is very lightweight and can be installed on a single server or VM.</p>
<p>It includes scalable hosting using Docker containers, continuous deployment with Git, and other popular DevOps tools.</p>
<p>Dokku also offers a variety of features, such as support for multiple languages, custom domains, automated deployments, and many more.</p>
<p>You can easily connect Postgres databases and even file storage to your applications.</p>
<p>You can check out more information at <a target="_blank" href="https://dokku.com/">https://dokku.com/</a> or the documentation at: <a target="_blank" href="https://dokku.com/docs/getting-started/installation/">https://dokku.com/docs/getting-started/installation/</a>.</p>
<p>You can also show some love for the <a target="_blank" href="https://github.com/dokku/dokku">open-source GitHub project here</a>. </p>
<h2 id="heading-how-to-install-dokku">How to Install Dokku</h2>
<p>In order to install Dokku you will need a Linux VPS and a domain name. </p>
<p>You can install and use Dokku without a domain name but it is much simpler using a domain. I recommend a cloud VPS because it makes it easier to access and configure.</p>
<p>When connecting a domain, either a single domain or a wildcard can be associated with the server's IP.</p>
<p>I will be using a VPS hosted on <a target="_blank" href="https://hetzner.cloud/">Hetzner</a> with Ubuntu 20.04 installed.</p>
<p>We first start by making sure that our system is up to date with these commands:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Update the linux installation</span>
$ sudo apt update
$ sudo apt upgrade -y
</code></pre>
<p>Then we can download and run the installation script for Dokku:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Install Dokku with the install script</span>
$ wget https://raw.githubusercontent.com/dokku/dokku/v0.26.8/bootstrap.sh;
$ sudo DOKKU_TAG=v0.26.8 bash bootstrap.sh

--&gt; Ensuring we have the proper dependencies
--&gt; Note: Installing dokku <span class="hljs-keyword">for</span> the first time will result <span class="hljs-keyword">in</span> removal of
    files <span class="hljs-keyword">in</span> the nginx <span class="hljs-string">'sites-enabled'</span> directory. Please manually
    restore any files that may be removed after the installation and
    web setup is complete.

    Installation will <span class="hljs-built_in">continue</span> <span class="hljs-keyword">in</span> 10 seconds.

    [...........]

    --&gt; Running post-install dependency installation

 ! Setup a user<span class="hljs-string">'s ssh key for deployment by passing in the public ssh key as shown:

     echo '</span>CONTENTS_OF_ID_RSA_PUB_FILE<span class="hljs-string">' | dokku ssh-keys:add admin</span>
</code></pre>
<p>The installation script will install Docker and all necessary dependencies and also Dokku itself, as seen in the code above.</p>
<p>After the installation is complete we need to assign the SSH keys to access and also configure our domain name.</p>
<p>In case you have set up access to your VPS with SSH (which you should) then you already have the necessary keys – you just need to add them to Dokku:</p>
<pre><code># Assign SSH key to Dokku
$ cat ~<span class="hljs-regexp">/.ssh/</span>authorized_keys | dokku ssh-keys:add admin

<span class="hljs-attr">SHA256</span>:<span class="hljs-number">6</span>O1TLVOUkWV+zmTWXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
</code></pre><p>In case you don't already have an SSH key in the server, then you need to generate a key pair:</p>
<pre><code># Generate SSH key
$ ssh-keygen

Generating public/private rsa key pair.
Enter file <span class="hljs-keyword">in</span> which to save the key (<span class="hljs-regexp">/root/</span>.ssh/id_rsa):
Enter passphrase (empty <span class="hljs-keyword">for</span> no passphrase):
Enter same passphrase again:
Your identification has been saved <span class="hljs-keyword">in</span> /root/.ssh/id_rsa
Your public key has been saved <span class="hljs-keyword">in</span> /root/.ssh/id_rsa.pub
The key fingerprint is:
SHA256:<span class="hljs-number">7</span>T6BbRCVWjGtcSUXXXXXXXXXXXXXXXXXXXXXXXXXXXXX root@freeDokku
The key<span class="hljs-string">'s randomart image is:
+---[RSA 3072]----+
[.................]
|     . oS*.o . . |
[.................]
+----[SHA256]-----+</span>
</code></pre><p>Then you can add it to Dokku:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Add SSH key to Dokku</span>
$ dokku ssh-keys:add admin /root/.ssh/id_rsa.pub

SHA256:7T6BbRCVWjGtcSUXXXXXXXXXXXXXXXXXXXXXXXX
</code></pre>
<p>Next, and the final step, is to assign the domain for your Dokku installation. We do that with the command:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Set installation global domain</span>
$ dokku domains:set-global domain.com

-----&gt; Set domain.com
</code></pre>
<p>Make sure you replace 'domain.com' with your own domain, and that your domain name DNS points to the server's IP address.</p>
<p>And that is all you need to do to install and set up Dokku. It is really that simple.</p>
<p>You can now start adding your applications. </p>
<p>Let's see an example of that by adding a standard Django application in the next section.</p>
<h2 id="heading-how-to-create-your-application-in-dokku">How to Create Your Application in Dokku</h2>
<p>To create and deploy our first application, there is some preparation work we need to do on Dokku.</p>
<p>To deploy an application on Dokku, follow these steps:</p>
<ul>
<li>Create the application on Dokku, which means giving it a name.</li>
<li>Create the associate database (or other plugins, if needed). This will create and provision a database for use with an automatic DATABASE_URL added to the application for ease of deployment.</li>
<li>Push the necessary code to Dokku's application internal GitHub endpoint. This can include also the necessary release steps (like running Django migrations, for example).</li>
</ul>
<p>After the code is pushed, Dokku will generate any necessary Docker container and will run our application with any associated databases (or other plugins).</p>
<p>Now that we've covered the necessary steps, let's go through them in practice.</p>
<p>Let's start by creating our application. For this tutorial, I will create a very simple Django website that contains all the necessary logic for us to test Dokku. </p>
<p>We create an application on Dokku with this command (in the server where we installed Dokku):</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Creating our application on Dokku</span>
$ dokku apps:create djangotutorial

-----&gt; Creating djangotutorial...
</code></pre>
<p>By default, datastores (or databases) are not created when an application is created. </p>
<p>The datastores are handled by a series of plugins. You can <a target="_blank" href="https://dokku.com/docs/community/plugins/#official-plugins-beta">check here for all available plugins</a>.</p>
<p>For our application, we will create a Postgres datastore. Since by default no plugins are installed, we first need to install the Postgres plugin:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># install the postgres plugin</span>
<span class="hljs-comment"># plugin installation requires root, hence the user change</span>
sudo dokku plugin:install https://github.com/dokku/dokku-postgres.git
</code></pre>
<p>Then we can create our Postgres datastore:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Create a Postgres datastore</span>
$ dokku postgres:create djangotutorial_datastore

       Waiting <span class="hljs-keyword">for</span> container to be ready
       Creating container database
       Securing connection to database
=====&gt; Postgres container created: djangotutorial_datastore
=====&gt; djangotutorial_datastore postgres service information
       Config dir:          /var/lib/dokku/services/postgres/djangotutorial_datastore/data
       Config options:
       Data dir:            /var/lib/dokku/services/postgres/djangotutorial_datastore/data
       Dsn:                 postgres://postgres:ea706cc108c805d5124d134d934024c5@dokku-postgres-djangotutorial-datastore:5432/djangotutorial_datastore
       Exposed ports:       -
       Id:                  782a04fe6bbd25958752c17c304358fd5ec1f3c54d6d53175b6481b3b957d94b
       Internal ip:         172.17.0.5
       Links:               -
       Service root:        /var/lib/dokku/services/postgres/djangotutorial_datastore
       Status:              running
       Version:             postgres:14.1
</code></pre>
<p>We can check that our Docker container for the datastore is already up and running with:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Check running containers</span>
$ docker ps

CONTAINER ID   IMAGE                      COMMAND                  CREATED              STATUS              PORTS      NAMES
782a04fe6bbd   postgres:14.1              <span class="hljs-string">"docker-entrypoint.s…"</span>   About a minute ago   Up About a minute   5432/tcp   dokku.postgres.djangotutorial_datastore
</code></pre>
<p>Now that we have the datastore up and running, we need to associate it with our application:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Associate datastore with the application</span>
$ dokku postgres:link djangotutorial_datastore djangotutorial

-----&gt; Setting config vars
       DATABASE_URL:  postgres://postgres:ea706cc108c805d5124d134d934024c5@dokku-postgres-djangotutorial-datastore:5432/djangotutorial_datastore
-----&gt; Restarting app djangotutorial
 !     App image (dokku/djangotutorial:latest) not found
</code></pre>
<p>You can see that a DATABASE_URL is automatically created and associated with the application.</p>
<p>The example above mentions that our application image is not found because we haven't pushed any code to it yet.</p>
<p>We can check our application's environment variables to confirm that our DATABASE_URL is present:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Checking an application environment variables</span>
$ dokku config:show djangotutorial

=====&gt; djangotutorial env vars
DATABASE_URL:  postgres://postgres:ea706cc108c805d5124d134d934024c5@dokku-postgres-djangotutorial-datastore:5432/djangotutorial_datastore
</code></pre>
<p>We now have all the necessary configurations done on the Dokku side to support the deployment of our application.</p>
<p>Next, we will create the code for our application and deploy that to Dokku for an automated CI/CD pipeline.</p>
<h2 id="heading-how-to-create-our-application-code-on-pycharm">How to Create Our Application Code on PyCharm</h2>
<p>Before we can deploy an application, we need to have its source code to push to Dokku.</p>
<p>For this tutorial, we are going to create a very simple Django application that shows also the use of the Postgres database.</p>
<p>We will be using PyCharm as our IDE to create and manage our project.</p>
<p>We create a new project in PyCharm – let's call it 'DjangoTutorial':</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/PyCharm-NewProject.png" alt="Image" width="600" height="400" loading="lazy">
<em>Creating a new project on PyCharm - Screenshot by author</em></p>
<p>I personally prefer to create new projects with a virtual environment already in place, which makes life much easier.</p>
<p>If you created the project with a default main.py file (like I did because I keep forgetting to remove the checkmark), you can safely delete it now. We are not going to use it.</p>
<p>The first step is, of course, to install Django so we can build our application. We do that install using pip:</p>
<pre><code>$ pip install django

Collecting django
  Downloading Django<span class="hljs-number">-4.0</span><span class="hljs-number">.2</span>-py3-none-any.whl (<span class="hljs-number">8.0</span> MB)
     |████████████████████████████████| <span class="hljs-number">8.0</span> MB <span class="hljs-number">6.4</span> MB/s
Collecting sqlparse&gt;=<span class="hljs-number">0.2</span><span class="hljs-number">.2</span>
  Using cached sqlparse<span class="hljs-number">-0.4</span><span class="hljs-number">.2</span>-py3-none-any.whl (<span class="hljs-number">42</span> kB)
Collecting tzdata
  Using cached tzdata<span class="hljs-number">-2021.5</span>-py2.py3-none-any.whl (<span class="hljs-number">339</span> kB)
Collecting asgiref&lt;<span class="hljs-number">4</span>,&gt;=<span class="hljs-number">3.4</span><span class="hljs-number">.1</span>
  Downloading asgiref<span class="hljs-number">-3.5</span><span class="hljs-number">.0</span>-py3-none-any.whl (<span class="hljs-number">22</span> kB)
Installing collected packages: tzdata, sqlparse, asgiref, django
Successfully installed asgiref<span class="hljs-number">-3.5</span><span class="hljs-number">.0</span> django<span class="hljs-number">-4.0</span><span class="hljs-number">.2</span> sqlparse<span class="hljs-number">-0.4</span><span class="hljs-number">.2</span> tzdata<span class="hljs-number">-2021.5</span>
</code></pre><p>Then we create our Django project with:</p>
<pre><code>$ django-admin startproject DjangoTutorial .
</code></pre><p>Notice the '.' at the end of the command. I like to use that so that it creates the project in the current directory instead of creating an extra sub-directory.</p>
<p>You should now have a project structure like this in PyCharm:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/PyCharm-Project.png" alt="Image" width="600" height="400" loading="lazy">
<em>PyCharm folder structure for our Django application - screenshot by the author</em></p>
<p>We can run our project with the standard Django run:</p>
<pre><code>$ python manage.py runserver   

Watching <span class="hljs-keyword">for</span> file changes <span class="hljs-keyword">with</span> StatReloader
Performing system checks...

System check identified no issues (<span class="hljs-number">0</span> silenced).

You have <span class="hljs-number">18</span> unapplied migration(s). Your project may not work properly until you apply the migrations <span class="hljs-keyword">for</span> app(s): admin, auth, contenttypes, sessions.
Run <span class="hljs-string">'python manage.py migrate'</span> to apply them.
February <span class="hljs-number">02</span>, <span class="hljs-number">2022</span> - <span class="hljs-number">16</span>:<span class="hljs-number">49</span>:<span class="hljs-number">27</span>
Django version <span class="hljs-number">4.0</span><span class="hljs-number">.2</span>, using settings <span class="hljs-string">'DjangoTutorial.settings'</span>
Starting development server at http:<span class="hljs-comment">//127.0.0.1:8000/</span>
Quit the server <span class="hljs-keyword">with</span> CTRL-BREAK.
</code></pre><p>We have not yet applied our migrations, so we will do that next after we discuss the database configuration for both local and Dokku access.</p>
<p>Navigating to the link <a target="_blank" href="http://127.0.0.1:8000/">http://127.0.0.1:8000/</a>, we can now access our standard Django welcome page:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Django.png" alt="Image" width="600" height="400" loading="lazy">
<em>Django welcome page - screenshot by the author</em></p>
<p>We have our Django installation up and running so now we can start building the rest of the project.</p>
<p>Like most projects, we will need to store data in a database (or database using the Dokku naming).</p>
<p>We also want to be able to debug and run our application locally on the development machine (using a local database, in this SQLite) and run it on the cloud with Dokku using the Postgres database. </p>
<p>This means we need to change some configuration in our settings.py to be able to support both use cases without us needing to change any flags or configs every time.</p>
<p>We start by installing the package dj-database-url with:</p>
<pre><code># Install packages <span class="hljs-keyword">for</span> the database url
$ pip install dj-database-url
$ pip install psycopg2


# We also install <span class="hljs-built_in">this</span> package to be able to use environment variables
$ pip install python-decouple
</code></pre><p>This package enables us to have a Django database connection dictionary, populated with all the data by simply specifying a database URL.</p>
<p>With the package install, let's update the configuration on the settings.py:</p>
<pre><code class="lang-python"><span class="hljs-comment"># We need to add this import at the beginning to use environment variables</span>
<span class="hljs-keyword">import</span> dj_database_url
<span class="hljs-keyword">from</span> decouple <span class="hljs-keyword">import</span> config
<span class="hljs-keyword">from</span> django.conf.global_settings <span class="hljs-keyword">import</span> DATABASES

.....

<span class="hljs-comment"># Let's also updated the allowed host so we can use it later on</span>
ALLOWED_HOSTS = config(<span class="hljs-string">'ALLOWED_HOSTS'</span>).split(<span class="hljs-string">','</span>)

.....

<span class="hljs-comment"># We replace the default database configuration from Django with this one</span>
<span class="hljs-comment"># Database</span>
<span class="hljs-comment"># https://docs.djangoproject.com/en/4.0/ref/settings/#databases</span>

<span class="hljs-comment"># DATABASE URL</span>
DATABASES[<span class="hljs-string">'default'</span>] = dj_database_url.parse(config(<span class="hljs-string">'DATABASE_URL'</span>),conn_max_age=<span class="hljs-number">600</span>)
</code></pre>
<p>We will also need to create '.env' file in the root dir of our project:</p>
<pre><code>#HOST SETTINGS
ALLOWED_HOSTS = <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>

#DATABASE SETTINGS
DATABASE_URL=<span class="hljs-string">'sqlite:///db.sqlite3'</span>
</code></pre><p>As you can see, with this change we can use the database URL from the local '.env' file on the local development machine, and then on Dokku it will automatically use the already defined DATABASE_URL that was created when we linked the datastore to the application on Dokku.</p>
<p>We can now create our first (and only) web page of this tutorial), a simple counter that stores and read the value from the database.</p>
<p>Let's create a separate application to contain our logic:</p>
<pre><code class="lang-bash">$ python manage.py startapp counter
</code></pre>
<p>We now should have a new folder called 'counter' in our project. Let's add a new model by opening the 'models.py' file:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.db <span class="hljs-keyword">import</span> models


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Counter</span>(<span class="hljs-params">models.Model</span>):</span>
    count = models.IntegerField(default=<span class="hljs-number">0</span>)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__str__</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-keyword">return</span> self.count
</code></pre>
<p>We can now add a new URL to load our counter page. We do that by adding a new file called 'urls.py' to our 'counter' folder:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.urls <span class="hljs-keyword">import</span> path
<span class="hljs-keyword">from</span> . <span class="hljs-keyword">import</span> views

urlpatterns = [
    path(<span class="hljs-string">'counter/'</span>, views.counter, name=<span class="hljs-string">'counter'</span>),
]
</code></pre>
<p>We now have both the model and the URL to load our test page. All we need now is the view and HTML template to render the page.</p>
<p>Let's create the view by editing the 'views.py' file:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.shortcuts <span class="hljs-keyword">import</span> render
<span class="hljs-keyword">from</span> .models <span class="hljs-keyword">import</span> Counter


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">counter</span>(<span class="hljs-params">request</span>):</span>
    counter_value = Counter.objects.last()

    <span class="hljs-keyword">if</span> counter_value <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:
        counter_value = Counter(count=<span class="hljs-number">0</span>)
        counter_value.save()

    <span class="hljs-keyword">if</span> request.method == <span class="hljs-string">'POST'</span>:
        counter_value.count += <span class="hljs-number">1</span>
        counter_value.save()

    <span class="hljs-keyword">return</span> render(request, <span class="hljs-string">'counter.html'</span>, {<span class="hljs-string">'counter'</span>: counter_value.count})
</code></pre>
<p>Now we can create our HTML template to show the counter value on the page. We create a new file called 'counter.html' inside a new 'templates' folder:</p>
<pre><code class="lang-html"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Counter<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">method</span>=<span class="hljs-string">"post"</span>&gt;</span>
      {%csrf_token%}
    <span class="hljs-tag">&lt;<span class="hljs-name">h4</span>&gt;</span>Counter value is: {{ counter }}<span class="hljs-tag">&lt;/<span class="hljs-name">h4</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"submit"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"Increase Counter"</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>The last step is to add our newly created application to the 'settings.py' file in order for Django to recognize it:</p>
<pre><code>.....

INSTALLED_APPS = [
    <span class="hljs-string">'counter'</span>,
    <span class="hljs-string">'django.contrib.admin'</span>,
    <span class="hljs-string">'django.contrib.auth'</span>,
    <span class="hljs-string">'django.contrib.contenttypes'</span>,
    <span class="hljs-string">'django.contrib.sessions'</span>,
    <span class="hljs-string">'django.contrib.messages'</span>,
    <span class="hljs-string">'django.contrib.staticfiles'</span>,
]

.....
</code></pre><p>And the URL to our main URLs file:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.contrib <span class="hljs-keyword">import</span> admin
<span class="hljs-keyword">from</span> django.urls <span class="hljs-keyword">import</span> path, include

urlpatterns = [
    path(<span class="hljs-string">''</span>, include(<span class="hljs-string">'counter.urls'</span>)),
    path(<span class="hljs-string">'admin/'</span>, admin.site.urls),
]
</code></pre>
<p>With all the necessary code and HTML in place, we can now create and run our migrations to create our new model in the database. We first do that on the local server by running:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Create and run migrations</span>
$ python manage.py makemigrations
$ python manage.py migrate

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, counter, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying counter.0001_initial... OK
  Applying sessions.0001_initial... OK
</code></pre>
<p>As you can see, we not only applied the migrations for our new application but we also run the initial migrations for the other Django applications since this was the first time we ran the migrations.</p>
<p>We can again run our server locally and we should be able to access the URL <a target="_blank" href="http://127.0.0.1:8000/counter/">http://127.0.0.1:8000/counter/</a> and increment the counter:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/CounterPage_Local.gif" alt="Image" width="600" height="400" loading="lazy">
<em>Running our counter application - GIF by the author</em></p>
<p>As you can see, reloading the page keeps our counter value, meaning that the value is been stored in the database with our model.</p>
<h2 id="heading-how-to-deploy-our-application-to-dokku">How to Deploy Our Application to Dokku</h2>
<p>We now have a very simple application running with database integration to store our counter value.</p>
<p>We are ready to deploy it to the cloud so we can test it there and make sure our database is also working in the cloud.</p>
<p>Before we do the Git push to deploy the code to Dokku, we need to do some preparation:</p>
<ul>
<li>Install our web server (gunicorn)</li>
<li>Create our requirements file (for our packages)</li>
<li>Create our Procfile (for our deployment commands)</li>
</ul>
<p>Let's start with installing our web server to use in the cloud:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Install our web server</span>
$ pip install gunicorn
</code></pre>
<p>With our packages in place we can now create our requirements file with:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Create requirements file</span>
$ pip freeze &gt; requirements.txt
</code></pre>
<p>Now we need to create the 'Procfile'. This file is used by Dokku to determine which commands to run on deployment and after deployment. </p>
<p>So let's create a new file called 'Procfile' in the root directory with the contents:</p>
<pre><code>web: gunicorn DjangoTutorial.wsgi
<span class="hljs-attr">release</span>: python manage.py migrate
</code></pre><p>We have created two commands for Dokku to run:</p>
<ul>
<li>release – this command is executed on the deployment of our application in Dokku. We use it to migrate our database.</li>
<li>web – this command allows Dokku to know which webserver to run to allow access to the application.</li>
</ul>
<p>Finally, to make sure that we can collect any static files when our code is deployed to Dokku, we need to create a new directory called 'static' on the root directory. Inside we create an empty file called '.gitkeep' (this will allow us to add the directory to the Git repository later).</p>
<p>We also need to add this path for the static files to our 'settings.py' file:</p>
<pre><code class="lang-python"><span class="hljs-comment"># Static files (CSS, JavaScript, Images)</span>
<span class="hljs-comment"># https://docs.djangoproject.com/en/4.0/howto/static-files/</span>

STATIC_URL = <span class="hljs-string">'static/'</span>
STATIC_ROOT = BASE_DIR / <span class="hljs-string">"static"</span>
</code></pre>
<p>Now all the files and logic are in place and we can deploy to Dokku with a standard Git push. Let's check our current file structure:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/PyCharm-FolderStrcuture-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>PyCharm folder structure - screenshot by the author</em></p>
<p>To be able to push our code to Dokku, we need to add our project to a Git repository.</p>
<p>Since we don't want to push all the files from our folder structure to the Dokku git repository, we create a '.gitignore' to exclude certain files and directories. I use the contents of this excellent Gist to populate the file:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="3cf91616c9f3bbc3d1339adfc707b08a">
        <script src="https://gist.github.com/MOOOWOOO/3cf91616c9f3bbc3d1339adfc707b08a.js"></script></div><p>We can now initialize and commit our code to a Git repository locally:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Initialize repository</span>
$ git init -b main

<span class="hljs-comment"># Add and commit our files</span>
$ git add . &amp;&amp; git commit -m <span class="hljs-string">"initial commit"</span>

[main (root-commit) e77a16a] initial commit
 20 files changed, 438 insertions(+)       
 create mode 100644 .gitignore
 create mode 100644 DjangoTutorial/__init__.py
 create mode 100644 counter/tests.py
 create mode 100644 counter/urls.py
 create mode 100644 counter/views.py
 create mode 100644 db.sqlite3
 create mode 100644 manage.py
 create mode 100644 requirements.txt
</code></pre>
<p>With our repository committed, we can now push it to a remote repository, that is the Dokku Git repository for our application:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Adding our remote repository (replace domain.com with your domain name)</span>
$ git remote add dokku dokku@domain.com:djangotutorial

<span class="hljs-comment"># Time to push our code to the remote repository</span>
$ git push dokku main

Enumerating objects: 34, <span class="hljs-keyword">done</span>.
Counting objects: 100% (34/34), <span class="hljs-keyword">done</span>.
Delta compression using up to 8 threads
Compressing objects: 100% (31/31), <span class="hljs-keyword">done</span>.
Writing objects: 100% (34/34), 11.41 KiB | 402.00 KiB/s, <span class="hljs-keyword">done</span>.
Total 34 (delta 7), reused 0 (delta 0)
-----&gt; Set main to DOKKU_DEPLOY_BRANCH.
-----&gt; Cleaning up...
-----&gt; Building djangotutorial from herokuish
-----&gt; Adding BUILD_ENV to build environment...
       BUILD_ENV added successfully
-----&gt; Python app detected
-----&gt; No Python version was specified. Using the buildpack default: python-3.9.9
       To use a different version, see: https://devcenter.heroku.com/articles/python-runtimes
-----&gt; No change <span class="hljs-keyword">in</span> requirements detected, installing from cache
-----&gt; Installing python-3.9.9
-----&gt; Installing pip 21.3.1, setuptools 57.5.0 and wheel 0.37.0
-----&gt; Installing SQLite3
-----&gt; Installing requirements with pip
       Collecting asgiref==3.5.0
       Downloading asgiref-3.5.0-py3-none-any.whl (22 kB)
       Collecting dj-database-url==0.5.0
       Downloading dj_database_url-0.5.0-py2.py3-none-any.whl (5.5 kB)
       Collecting Django==4.0.2
       Downloading Django-4.0.2-py3-none-any.whl (8.0 MB)
       Collecting gunicorn==20.1.0
       Downloading gunicorn-20.1.0-py3-none-any.whl (79 kB)
       Collecting psycopg2==2.9.3
       Downloading psycopg2-2.9.3.tar.gz (380 kB)
       Preparing metadata (setup.py): started
       Preparing metadata (setup.py): finished with status <span class="hljs-string">'done'</span>
       Collecting python-decouple==3.5
       Downloading python_decouple-3.5-py3-none-any.whl (9.6 kB)
       Collecting sqlparse==0.4.2
       Downloading sqlparse-0.4.2-py3-none-any.whl (42 kB)
       Collecting tzdata==2021.5
       Downloading tzdata-2021.5-py2.py3-none-any.whl (339 kB)
       Building wheels <span class="hljs-keyword">for</span> collected packages: psycopg2
       Building wheel <span class="hljs-keyword">for</span> psycopg2 (setup.py): started
       Building wheel <span class="hljs-keyword">for</span> psycopg2 (setup.py): finished with status <span class="hljs-string">'done'</span>
       Created wheel <span class="hljs-keyword">for</span> psycopg2: filename=psycopg2-2.9.3-cp39-cp39-linux_x86_64.whl size=579484 sha256=9d6a2810a5d766738526d6f411e5e9ce514cce882b6c80a47a13c02dc7529e02
       Stored <span class="hljs-keyword">in</span> directory: /tmp/pip-ephem-wheel-cache-8k0chg5g/wheels/b3/a1/6e/5a0e26314b15eb96a36263b80529ce0d64382540ac7b9544a9
       Successfully built psycopg2
       Installing collected packages: sqlparse, asgiref, tzdata, python-decouple, psycopg2, gunicorn, Django, dj-database-url
       Successfully installed Django-4.0.2 asgiref-3.5.0 dj-database-url-0.5.0 gunicorn-20.1.0 psycopg2-2.9.3 python-decouple-3.5 sqlparse-0.4.2 tzdata-2021.5
-----&gt; $ python manage.py collectstatic --noinput
       128 static files copied to <span class="hljs-string">'/tmp/build/static'</span>.

-----&gt; Discovering process types
       Procfile declares types -&gt; release, web
-----&gt; Releasing djangotutorial...
-----&gt; Checking <span class="hljs-keyword">for</span> predeploy task
       No predeploy task found, skipping
-----&gt; Checking <span class="hljs-keyword">for</span> release task
-----&gt; Executing release task from Procfile: python manage.py migrate
=====&gt; Start of djangotutorial release task (a602cab30) output
       Operations to perform:
         Apply all migrations: admin, auth, contenttypes, counter, sessions
       Running migrations:
         Applying contenttypes.0001_initial... OK
         Applying auth.0001_initial... OK
         Applying admin.0001_initial... OK
         Applying admin.0002_logentry_remove_auto_add... OK
         Applying admin.0003_logentry_add_action_flag_choices... OK
         Applying contenttypes.0002_remove_content_type_name... OK
         Applying auth.0002_alter_permission_name_max_length... OK
         Applying auth.0003_alter_user_email_max_length... OK
         Applying auth.0004_alter_user_username_opts... OK
         Applying auth.0005_alter_user_last_login_null... OK
         Applying auth.0006_require_contenttypes_0002... OK
         Applying auth.0007_alter_validators_add_error_messages... OK
         Applying auth.0008_alter_user_username_max_length... OK
         Applying auth.0009_alter_user_last_name_max_length... OK
         Applying auth.0010_alter_group_name_max_length... OK
         Applying auth.0011_update_proxy_permissions... OK
         Applying auth.0012_alter_user_first_name_max_length... OK
         Applying counter.0001_initial... OK
         Applying sessions.0001_initial... OK
=====&gt; End of djangotutorial release task (a602cab30) output
-----&gt; App Procfile file found
=====&gt; Processing deployment checks
       No CHECKS file found. Simple container checks will be performed.
       For more efficient zero downtime deployments, create a CHECKS file. See https://dokku.com/docs/deployment/zero-downtime-deploys/ <span class="hljs-keyword">for</span> examples
-----&gt; Deploying djangotutorial via the docker-local scheduler...
-----&gt; Deploying web (count=1)
       Attempting pre-flight checks (web.1)
       Waiting <span class="hljs-keyword">for</span> 10 seconds (web.1)
       Default container check successful (web.1)
-----&gt; Deploying release (count=0)
-----&gt; Running post-deploy
-----&gt; Creating new app virtual host file...
-----&gt; Configuring djangotutorial.domain.com...(using built-in template)
-----&gt; Creating http nginx.conf
       Reloading nginx
-----&gt; Renaming containers
       Renaming container djangotutorial.web.1.upcoming-7101 (f8d229ebd8bc) to djangotutorial.web.1
-----&gt; Checking <span class="hljs-keyword">for</span> postdeploy task
       No postdeploy task found, skipping
-----&gt; Updated schedule file
=====&gt; Application deployed:
       http://djangotutorial.domain.com

To domain.com:djangotutorial
 * [new branch]      main -&gt; main
</code></pre>
<p>We have just deployed our application to Dokku. </p>
<p>What just happened? Well, Dokku did a lot of work for us:</p>
<ul>
<li>Installed Python</li>
<li>Installed the requirements</li>
<li>Collected the static files</li>
<li>Performed the migrations</li>
<li>And finally started a gunicorn server to deploy our application </li>
</ul>
<p>If you had a permission error, then your private key should be registered within your local development environment. If you get a <code>permission denied</code> error when pushing, you can register your private key as follows: <code>ssh-add -k ~/&lt;your private key&gt;</code>.</p>
<p>You may also see an error regarding the ALLOWED_HOSTS when accessing the application. In that case, all you need to do is to run the following command on the Dokku server to set the environment variable to the correct value:</p>
<pre><code># <span class="hljs-built_in">Set</span> ALLOWED_HOSTS environment variable (make sure to use your domain name)
$ dokku config:set djangotutorial ALLOWED_HOSTS=djangotutorial.domain.com
</code></pre><p>We can now access and test our application at the above URL:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/PageCounter_Server.gif" alt="Image" width="600" height="400" loading="lazy">
<em>Running our counter application on Dokku - GIF by the author</em></p>
<p>Congratulations, you just deployed your application on Dokku. </p>
<h2 id="heading-how-to-add-ssl-with-lets-encrypt">How to Add SSL with Let's Encrypt</h2>
<p>One final configuration that we can do is to add SSL security to our application by installing a Let's Encrypt SSL certificate.</p>
<p>We can do this very easily on Dokku with the Let's Encrypt plugin:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Install the Let's Encrypt plugin</span>
sudo dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git

<span class="hljs-comment"># Configure the plugin (make sure to replace to your email)</span>
dokku config:<span class="hljs-built_in">set</span> --global DOKKU_LETSENCRYPT_EMAIL=email@domain.com

<span class="hljs-comment"># set a custom domain that you own for your application</span>
dokku domains:<span class="hljs-built_in">set</span> djangotutorial djangotutorial.your.domain.com

<span class="hljs-comment"># Enable Let's Encrypt</span>
dokku letsencrypt:<span class="hljs-built_in">enable</span> djangotutorial

<span class="hljs-comment"># Enable Let's Encrypt auto-renewal</span>
dokku letsencrypt:cron-job --add
</code></pre>
<p>Now we have a more secure application. After all, our counter is very important.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Using a PaaS makes a developer's life easier when building web applications. </p>
<p>You can use hosted PaaS like Heroku and there are many others, so the choice is there.</p>
<p>But there are some main drawbacks:</p>
<ul>
<li>Price – hosted solutions might have limits in terms of database storage or file storage, among others</li>
<li>You don't control the hosting where the PaaS is deployed. Recent examples of AWS shows that not even the biggest hosting is free of problems.</li>
</ul>
<p>You can work around these issues by self-hosting your PaaS.</p>
<p>This allows for more control in terms of pricing. You can use hosting provider like <a target="_blank" href="https://www.digitalocean.com/">Digital Ocean</a>, <a target="_blank" href="https://hetzner.cloud/">Hetzner</a>, and others who have quite cheap VPSs that work perfectly with Dokku.</p>
<p>There are no database limits. The only limits you might have are memory and disk space, but you can always upgrade your VPS for a smaller price than getting a new database at Heroku.</p>
<p>Dokku is easy to install and like we saw. Creating and deploying an application is a 3 step process:</p>
<ul>
<li>Create an application on Dokku</li>
<li>Create a datastore on Dokku (if needed, like Postgres) and link to the application</li>
<li>Deploy your code to Dokku with Git</li>
</ul>
<p>Additionally, you might need to configure some environment variables and SSL certificates, but that is all. </p>
<p>Dokku is really the smallest PaaS implementation.</p>
<p>Full source code for the Django application is available at:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/nunombispo/DjangoTutorial">https://github.com/nunombispo/DjangoTutorial</a></div>
<p>Follow me on Twitter: <a target="_blank" href="https://twitter.com/DevAsService">https://twitter.com/DevAsService</a></p>
<p>Check out my website at: <a target="_blank" href="https://developer-service.io/">https://developer-service.io/</a></p>
<p>Or check out my blog at: <a target="_blank" href="https://blog.developer-service.io/">https://blog.developer-service.io/</a></p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
