Zero-Downtime Deployment for Elixir

Khimananda Oli 8 min read Programming and Languages
Zero-Downtime Deployment for Elixir

By Khimananda Oli | Last reviewed: August 2026

Achieving zero-downtime deployment for Elixir requires understanding the BEAM’s unique ability to run two versions of a module simultaneously. Unlike traditional frameworks that require full process restarts, Elixir allows you to update code in a running system without dropping connections or losing state. This capability is powerful but demands precise configuration; a misconfigured release can still cause brief outages during socket handoffs or database migration windows.

Load BalancerNode A (v1.2)DrainingNode B (v1.3)ActiveNode C (v1.3)ActivePostgreSQLShared StateRelease v1.3Artifact Store
Zero-downtime deployment for Elixir architecture: load balancer routes traffic to healthy nodes while one node drains during upgrade

How does zero-downtime deployment for Elixir actually work?

The foundation of zero-downtime deployment for Elixir lies in the Erlang/OTP platform's design. The BEAM virtual machine supports having two versions of any module loaded at once: the "current" version and the "old" version. When you perform a hot code upgrade, the system marks existing code as old and loads the new version as current. New function calls use the current version, while existing processes continue executing against the old version until they explicitly yield control via a receive loop or are migrated.

This mechanism differs fundamentally from typical web frameworks. In Node.js or Python, deploying new code means stopping the old process entirely and starting a new one, which inevitably drops in-flight requests unless you implement external orchestration. With Elixir, the runtime itself manages this transition internally. However, relying solely on hot upgrades is risky in modern containerized environments. Most production teams now combine OTP primitives with infrastructure-level rolling restarts, using the BEAM's clustering capabilities to coordinate graceful shutdowns across nodes.

For Phoenix applications specifically, you must also consider WebSocket connections and PubSub subscriptions. These long-lived processes don't automatically migrate during code swaps. Your deployment strategy needs to account for draining these connections gracefully, typically by signaling clients to reconnect after a brief delay while the backend transitions. I cover connection draining patterns extensively in my article on blue-green and canary deploys on Kubernetes, which applies directly to Elixir clusters.

How do you configure Elixir releases for safe hot upgrades?

Hot upgrades require explicit configuration in your release definition. Without proper appup files and relup scripts, the upgrade will fail or worse, silently corrupt state. Start by ensuring your mix.exs defines a valid release with version tracking enabled.

# mix.exs
def project do
  [
    app: :my_app,
    version: "1.3.0",
    releases: [
      my_app: [
        include_executables_for: [:unix],
        applications: [runtime_tools: :permanent],
        steps: [:assemble, :tar]
      ]
    ]
  ]
end

The critical piece is generating correct appup files. For most GenServers, you need to specify how to transform state between versions. Create or edit src/my_app.appup.src:

{"1.3.0",
 [{"1.2.0", [{add_module, MyApp.NewFeature},
             {update, MyApp.Worker, {advanced, []}}]}],
 [{"1.2.0", [{delete_module, MyApp.NewFeature},
             {update, MyApp.Worker, {advanced, []}}]}]
}.

The {advanced, []} tuple tells OTP to call your module's code_change/3 callback. Implement this function to handle state transformation:

def code_change(old_vsn, state, extra) do
  # Transform state from v1.2 format to v1.3 format
  new_state = Map.put(state, :new_field, default_value())
  {:ok, new_state}
end

A common mistake is forgetting that code_change runs synchronously during the upgrade. If your transformation takes too long or blocks, it can stall the entire release process. Keep transformations lightweight and defer heavy computation to async tasks. Always test upgrades locally using MIX_ENV=prod mix release.upgrade before attempting them in production. For deeper guidance on managing stateful services during transitions, see Kubernetes secrets management done right — secure secret rotation often accompanies code upgrades.

Release HandlerGenServer v1.2Appup ScriptNew Code v1.3suspendload new beamread instructionscode_change/3State Transformedv1.2 → v1.3resumeGenServer v1.3Running New Codeprovides new .beam
Elixir hot upgrade sequence: suspend → load new code → code_change transforms state → resume with new version

When should you use rolling restarts instead of hot upgrades?

Despite the elegance of hot upgrades, many production Elixir teams in 2026 prefer rolling restarts within Kubernetes or similar orchestrators. Hot upgrades carry inherent risk: a buggy code_change callback can leave processes in an inconsistent state that only manifests hours later. Rolling restarts trade some sophistication for predictability. Each pod terminates gracefully, finishes in-flight requests, then exits cleanly before its replacement becomes ready.

Use rolling restarts when your changes involve dependency updates, Erlang NIF modifications, or VM-level configuration changes. These cannot be handled by hot code loading. Also prefer rolling restarts if your team lacks deep OTP expertise; debugging a failed hot upgrade at 3 AM during an incident is significantly harder than debugging a standard crash loop. Configure your Kubernetes deployment with appropriate readiness probes to ensure new pods serve traffic only after completing initialization:

readinessProbe:
  httpGet:
    path: /health/ready
    port: 4000
  initialDelaySeconds: 10
  periodSeconds: 5
livenessProbe:
  httpGet:
    path: /health/live
    port: 4000
  initialDelaySeconds: 30
  periodSeconds: 10

Your Phoenix endpoint must distinguish between "alive" (process running) and "ready" (dependencies connected, caches warmed). Return 503 from /health/ready until all prerequisites are satisfied. This prevents the load balancer from routing requests to a node that hasn't finished booting. For comprehensive health check implementation patterns aligned with observability best practices, refer to the four golden signals of monitoring.

CriteriaHot UpgradeRolling Restart
Downtime RiskVery low if configured correctlyNear-zero with proper probes
ComplexityHigh (appup, code_change testing)Low (standard K8s rollout)
Dependency UpdatesNot supportedFully supported
State PreservationIn-memory state retainedRequires external persistence
Rollback SpeedInstant (revert to old code)Minutes (previous image tag)
Best ForBug fixes, pure logic changesInfra changes, deps, config

How do you handle database migrations during Elixir deployments?

Database migrations are the most common source of deployment-related downtime in Elixir applications, regardless of whether you use hot upgrades or rolling restarts. The rule is simple: never run destructive migrations concurrently with application code that expects the old schema. Adopt the expand-contract pattern for all schema changes.

  1. Expand: Add new columns or tables as nullable/optional. Deploy application code that writes to both old and new structures but reads from the old one. This migration is backward-compatible and safe to run before the code deploy.
  2. Migrate Data: Backfill existing records into the new structure using a separate background job or migration script. Monitor progress independently of application deployments.
  3. Contract: Once backfill completes and new code is stable, deploy code that reads from the new structure. Finally, remove deprecated columns in a subsequent release.

Never run mix ecto.migrate as part of your application startup in production. This creates a race condition where multiple nodes attempt migrations simultaneously, potentially causing lock contention or partial failures. Instead, run migrations as a dedicated init container or pre-deploy job that completes before application pods start. For PostgreSQL-specific backup and recovery procedures that complement safe migration practices, consult PostgreSQL backup and restore with pg_dump.

What monitoring validates successful zero-downtime deployments?

You cannot claim zero-downtime deployment for Elixir without instrumentation proving it. Define clear SLIs around error rate and latency during deployment windows. A truly zero-downtime deploy shows no spike in 5xx errors and no p99 latency increase beyond normal variance. Instrument your Phoenix endpoints with OpenTelemetry to capture per-request traces across the deployment boundary.

Set up deployment-aware dashboards that overlay release timestamps onto metric graphs. If you see even a single error burst correlated with a pod termination or hot upgrade event, your zero-downtime claim is invalid. Common failure points include WebSocket reconnection storms, cache cold starts on new nodes, and database connection pool exhaustion during rolling restarts. Pre-warm caches and stagger pod terminations to smooth out these transitions. Effective alerting thresholds for deployment anomalies are covered in alerting with Prometheus Alertmanager.

Error Rate During Deployment Window0%2%4%6%8%Deployment TimelineDeploy StartDeploy EndTraditional: 6% spikeZero-Downtime: flatTraditional RestartZero-Downtime
Error rate comparison: traditional deployment shows significant spike during restart window versus flat line for zero-downtime deployment for Elixir

Implementing Reliable Zero-Downtime Deployment for Elixir

Successful zero-downtime deployment for Elixir combines BEAM-native capabilities with disciplined operational practices. Choose hot upgrades for targeted logic fixes where state preservation matters, and rolling restarts for broader changes involving dependencies or infrastructure. Always validate with real metrics, not assumptions. Test every upgrade path in staging with production-like traffic patterns before touching live systems. If your deployment still causes errors, instrument deeper rather than accepting intermittent failures as inevitable. For teams building Elixir systems in Nepal or globally, getting this right separates professional-grade platforms from fragile prototypes. Need help architecting resilient Elixir deployments? Contact me to discuss your specific requirements.

Frequently Asked Questions

Elixir uses hot code swapping and release upgrades to replace modules in running BEAM nodes without stopping processes, enabling true zero-downtime deployment for Elixir applications during production updates.

Mix release is built-in since Elixir 1.9 and sufficient for most zero-downtime deployment for Elixir workflows. Distillery offers advanced custom commands but adds complexity; prefer mix release unless you need specific plugin integrations or legacy support.

Yes. Configure readiness probes checking /health endpoints and set preStop hooks allowing graceful shutdown. Kubernetes replaces pods sequentially while Elixir handles connection draining, ensuring zero-downtime deployment for Elixir clusters without dropped requests.

Run backward-compatible migrations before deploying new code. Use expand-and-contract patterns: add columns first, deploy dual-write logic, backfill data, then remove old columns in subsequent releases to maintain zero-downtime deployment for Elixir safely.

Yes. Enable server: true in prod.exs, configure endpoint check_origin properly, and use SIGTERM handlers for graceful shutdown. Phoenix 1.7+ includes telemetry hooks that coordinate with load balancers during zero-downtime deployment for Elixir transitions.

Typically caused by missing appup files or incompatible state changes between versions. Ensure all changed modules have valid upgrade instructions and test releases locally using :release_handler before attempting zero-downtime deployment for Elixir in production environments.

It depends. Hot swapping avoids infrastructure duplication but requires careful version compatibility. Blue-green eliminates runtime upgrade risks at higher cost. Many teams combine both strategies for safer zero-downtime deployment for Elixir across critical services.

Monitor request latency percentiles, error rates, and active connection counts via telemetry during deployment. Use tools like LiveDashboard or Prometheus to confirm no spikes occur, validating genuine zero-downtime deployment for Elixir rather than assumed success.

EPMD manages node discovery for clustered BEAM instances. For zero-downtime deployment for Elixir, consider replacing it with DNS-based clustering via libcluster to avoid single-point failures during rolling upgrades across multiple hosts.

Costs vary by strategy. Hot swapping adds minimal overhead. Rolling updates require extra pod capacity during transitions. Blue-green doubles infrastructure temporarily. Budget 10-30% additional compute for reliable zero-downtime deployment for Elixir depending on traffic patterns.

Possible but risky. You must rely solely on BEAM hot upgrades with perfect appup coverage. Any incompatible change forces downtime. Most production zero-downtime deployment for Elixir setups use reverse proxies or service meshes for safety.

Keep previous release artifacts and use :release_handler.revert_to_old/0 if issues emerge. Always test rollback procedures in staging first. Automated health checks should trigger reverts automatically during zero-downtime deployment for Elixir failures.

Sign release tarballs, restrict SSH access to deployment nodes, and audit appup files for arbitrary code execution risks. Never run upgrades as root. Validate checksums before applying any zero-downtime deployment for Elixir to prevent supply chain attacks.

Yes, but each app needs coordinated versioning and compatible interfaces. Generate unified releases with mix release --umbrella and test cross-app dependencies thoroughly. Umbrella apps add complexity to zero-downtime deployment for Elixir requiring stricter CI validation.

Weekly at minimum. Regular practice catches configuration drift, stale appup files, and monitoring gaps. Treat every deploy as a drill to build team confidence and ensure your zero-downtime deployment for Elixir pipeline remains reliable under pressure.