DEV Community

Cover image for How Can Developers Deploy New Code Without Taking an Entire Website Offline?
Tanu Priya
Tanu Priya

Posted on

How Can Developers Deploy New Code Without Taking an Entire Website Offline?

Imagine running an online store during a major sale. Customers are browsing products, adding items to their carts, and completing payments when your development team needs to release an important update to the checkout system.

The update is ready, the tests are passing, and the team wants to ship it. But there is one problem: the application is already serving thousands of requests, and restarting the entire server could interrupt active sessions or prevent customers from completing their purchases.

This is where deployment becomes more than a matter of copying new files to a server. The challenge is to replace a running application without interrupting the people who depend on it.

Modern applications solve this problem by treating deployment as a gradual transition rather than a complete replacement. Instead of shutting everything down, developers prepare the new version, verify that it works, and move traffic toward it while keeping the existing version available.

The techniques behind this approach are surprisingly practical, and they are useful even for teams running relatively small applications.

Why Does a Deployment Take a Website Offline?

A production application rarely consists of a single process. It may include web servers, APIs, background workers, database connections, caches, and other services that must work together to handle requests.

When developers release a new version, they might need to replace application files, install dependencies, update configuration, or restart running processes. If the only application instance is stopped before its replacement becomes available, incoming requests have nowhere to go.

For example, consider a Node.js application running on a single server behind Nginx. If the team stops the Node.js process, replaces the code, and starts the application again, every request arriving during that interruption may fail or receive an error response.

The solution is not necessarily a more powerful server. It is a deployment process that keeps a working version available while the replacement is being prepared.

Several strategies can achieve this, but each solves the problem in a slightly different way.

1. Rolling Deployments: Update the Application Gradually

A rolling deployment replaces running application instances in small groups rather than restarting everything simultaneously.

Suppose an application runs across four instances behind a load balancer. All four currently serve version one, and the team wants to release version two. Instead of stopping all four instances, the deployment system updates one instance while the other three continue handling requests.

Once the updated instance starts successfully and passes its health checks, it can receive traffic. The deployment then moves to another instance and repeats the process until every instance runs the new version.

The benefit is straightforward: the application does not need to become completely unavailable just because some of its instances are being updated.

Rolling deployments are particularly useful for applications running on Kubernetes, Amazon ECS, or other platforms that support controlled replacement of application instances.

However, there is an important requirement. The remaining instances must have enough capacity to handle incoming traffic while the others are unavailable for updates. A deployment can still cause problems if every instance is restarted together, the application starts accepting traffic too early, or the available servers become overloaded.

Developers also need to consider compatibility between versions. During a rolling deployment, version one and version two may run simultaneously, so both versions must be able to work with the same database and shared services.

Learn more about rolling deployments in Amazon ECS.

2. Blue-Green Deployments: Prepare the Replacement Before Switching

Rolling deployments gradually replace existing instances, but some releases require a different approach. When a team introduces a major architectural change or wants to test a complete new environment before exposing it to customers, blue-green deployment can be a better choice.

The technique uses two environments. The blue environment runs the current production version, while the green environment hosts the version being prepared for release.

Developers deploy the new application to green, verify its configuration, test its APIs, and confirm that its essential features work. Meanwhile, users continue interacting with blue, which remains the active production environment.

Once green is ready, the routing layer switches production traffic to it. If the new release causes a serious problem, traffic can often be redirected to blue while the team investigates.

Think about an e-commerce platform introducing a redesigned checkout service. Rather than replacing the live checkout immediately, the team can prepare the new environment separately and validate it before making the switch.

This approach makes application rollback relatively simple because the previous environment remains available. The trade-off is infrastructure cost: running two environments may require additional computing resources, and both environments need suitable configuration and access to shared dependencies.

There is also a limitation that deserves attention. Switching traffic back to the old application does not reverse database changes, cancel completed transactions, or restore data that has already been modified. Blue-green deployment makes application rollback easier, but it does not eliminate the need for database compatibility and recovery planning.

Read AWS's guide to blue-green deployments.

3. Canary Releases: Test the New Version With Real Traffic

Passing automated tests does not guarantee that a release will behave correctly in production. Real users generate unexpected combinations of requests, devices, workloads, and usage patterns that are difficult to reproduce in a test environment.

Canary releases reduce this risk by sending a small portion of production traffic to the new version before expanding the rollout.

Imagine a service receiving 100,000 requests per day. Instead of moving every request to the new version immediately, the team might initially direct five percent of eligible traffic to it while the remaining requests continue using the stable version.

The team then compares important metrics between the two versions, including error rates, response times, resource consumption, and successful completion of important operations.

If the new version performs as expected, its share of traffic can gradually increase. If errors rise or response times deteriorate, the rollout can be paused and traffic returned to the stable version.

This is particularly valuable for services where a small defect could have a significant business impact, such as payment processing, authentication, or checkout.

Canary releases do require more than a traffic-splitting mechanism. Developers need reliable monitoring, clear criteria for deciding whether the release is healthy, and a way to stop or reverse the rollout when those criteria are not met.

There is another subtle consideration: five percent of traffic does not necessarily mean five percent of users or five percent of business risk. If traffic is distributed unevenly, the canary group might contain a disproportionate number of particular users or transactions.

A good rollout strategy therefore considers how traffic is selected, not just how much traffic is selected.

Explore the Kubernetes canary deployment tutorial.

4. Load Balancers and Health Checks Keep Requests Flowing

Rolling deployments, blue-green deployments, and canary releases all depend on controlling where requests go. This is why load balancers and health checks are important parts of a reliable deployment architecture.

A load balancer distributes incoming requests among application instances that are available to serve them. When an instance needs to be updated, the routing layer can stop sending it new requests while the deployment process replaces it.

Health checks help determine when an instance is ready to receive traffic again. However, there is a difference between a process being alive and an application being ready.

A Node.js process might start successfully but still be unable to handle requests because its database connection has failed, its configuration is incomplete, or a required dependency is unavailable.

A readiness check should therefore verify the conditions that matter for serving traffic, without becoming so complicated that a temporary dependency issue unnecessarily removes every instance from service.

Graceful shutdown is equally important. When an old instance is removed, it should stop accepting new requests and receive enough time to finish existing work before terminating. Otherwise, a deployment could interrupt an active request even though other instances remain healthy.

These details are easy to overlook, but they often determine whether a deployment is genuinely smooth or merely appears successful during testing.

5. CI/CD Pipelines Make Releases Repeatable

A deployment process that depends on someone manually copying files, restarting services, checking logs, and remembering every configuration step becomes difficult to maintain as an application grows.

Continuous integration and continuous delivery (CI/CD) reduce this complexity by turning the release process into a repeatable workflow.

A typical pipeline begins by running automated tests and checking the code. It then builds a versioned application artifact or container image, deploys it to a staging environment, and runs smoke tests to verify that the application starts and its essential endpoints respond correctly.

After these checks pass, the pipeline releases the new version using a suitable deployment strategy. Monitoring can then evaluate the rollout, and automated systems can pause or roll back a release when configured failure conditions are detected.

Tools such as GitHub Actions, Jenkins, Kubernetes, and cloud deployment services can automate different parts of this process.

Automation does not make every release safe by itself. A test suite can miss a bug, a staging environment may differ from production, and a successful build says little about how the application will behave under unexpected traffic.

What automation provides is consistency. The same checks run every time, the deployed version can be identified, and the team can investigate failures without relying entirely on someone's memory of the last release.

A useful goal is to make deployments routine enough that releasing code no longer feels like a high-risk manual operation.

6. Database Changes Can Break an Otherwise Perfect Deployment

One of the most easily overlooked parts of deployment is the database. Application instances can be replaced gradually, but a database change may affect both old and new versions at the same time.

Suppose an application stores a user's name in a column called full_name. The new version expects that column to be called display_name.

If the team renames the column immediately, the new application might work while the old application starts failing. During a rolling deployment, both versions can be running simultaneously, so the database must remain compatible with both.

A safer technique is known as an expand-and-contract migration.

First, introduce the new database structure without removing the existing one. Next, deploy application code that can work with the transitional schema. If data must be copied or transformed, perform that migration and validate the results before switching all reads and writes to the new structure.

Once the old application version is no longer running and no longer depends on the original column, the team can remove it in a later release.

This approach separates a potentially risky database change into smaller, more manageable steps.

The same principle applies to API contracts, message queues, and shared services. Whenever multiple versions may coexist, changes should remain compatible until the transition is complete.

Developers should also plan for failed migrations, data validation, backups, and recovery. Redirecting traffic to an older application cannot fix a database that has already been changed in an incompatible or destructive way.

7. Feature Flags Separate Deployment From Release

Another useful technique is to deploy code without immediately making the associated feature available to everyone.

Feature flags allow a team to control whether a particular feature is enabled at runtime. A new checkout flow, dashboard, or recommendation system can be included in a production deployment while remaining disabled for ordinary users.

Developers might enable it for internal testing first, then for a small group of customers, and finally for the wider user base. If the feature causes problems, they may be able to disable it without deploying the application again.

This separates two decisions that are often confused: when code reaches production and when users can actually access it.

For example, a team could deploy a redesigned checkout flow on Monday, test it internally on Tuesday, and enable it for a limited group of customers on Wednesday. The code is already present, but exposure is controlled independently.

Feature flags are not a substitute for sound deployment practices. Defective code can still cause problems if it executes before the flag is checked, and flags that are never removed can make the codebase increasingly difficult to maintain.

They work best when each flag has a clear purpose, an owner, and a plan for eventual removal.

What Does a Near-Zero-Downtime Deployment Look Like in a Real Application?

Consider a Node.js application running behind Nginx with several application instances and a PostgreSQL database. The team wants to release a new API endpoint and update the checkout flow.

Instead of stopping the existing application, the team first runs its automated tests and builds a versioned release. The deployment process starts new instances while the current instances continue serving requests. Readiness checks verify that the replacements can handle traffic before the routing layer begins directing requests toward them.

The database changes are introduced in a backward-compatible way, so both the old and new application versions can operate during the transition. Once the new instances are serving traffic, the team watches error rates, response times, database performance, and checkout completion.

If the release remains healthy, the rollout continues until the new version is fully deployed. The team can then enable the updated checkout feature for more users using a feature flag.

If the new version starts producing errors, the team can pause the rollout, route traffic back to the previous version where appropriate, and disable the affected feature. Any database or data changes still need their own recovery strategy.

The exact implementation depends on the application's size and infrastructure. A small application might use two Node.js processes on separate ports, with Nginx switching traffic between them. A larger service might use Kubernetes, a cloud load balancer, automated health checks, and a deployment pipeline.

The underlying principle is the same: prepare the replacement, verify that it works, and avoid removing the working version before the new one is ready.

Is Zero Downtime Actually Possible?

Near-zero downtime is a realistic engineering goal, but it is not a guarantee that every request will succeed during every release.

A deployment can still interrupt service if all application instances become unavailable together, the new version cannot coexist with the old database schema, or a shared dependency fails. A load balancer cannot distribute traffic to healthy instances if none exist, and a rollback cannot automatically recover data that has already been changed incorrectly.

There are also trade-offs. Blue-green deployments can require additional infrastructure, canary releases depend on reliable monitoring, and rolling deployments need enough spare capacity to handle traffic while instances are updated.

The right approach depends on the application's availability requirements, traffic patterns, architecture, and budget. A small portfolio website may tolerate an occasional short interruption, while a payment service may require carefully planned releases and extensive recovery procedures.

For teams just getting started, it is often more useful to establish a reliable deployment pipeline, meaningful health checks, and a safe rollback procedure than to introduce a complicated architecture immediately.

Final Thoughts

Deploying new code without taking an entire website offline is less about finding a special command and more about designing a system that can handle change while it is running.

Rolling deployments replace instances gradually. Blue-green deployments prepare a replacement environment before switching traffic. Canary releases test new versions with a limited portion of production traffic. Load balancers, health checks, graceful shutdown, compatible database migrations, and CI/CD pipelines help these strategies work together.

The most important lesson is that application availability depends on more than the deployment command itself. The database must tolerate the transition, the new version must be ready before receiving traffic, and the team must have a way to detect and recover from problems.

When these pieces are designed together, releasing new code becomes a controlled engineering process rather than an interruption that users have to experience.

The goal is not simply to deploy faster. It is to make improvements without unnecessarily disrupting the people who rely on the application.

Top comments (0)