
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
Deploying Elixir applications requires more than a basic container image; you need to handle distributed Erlang clustering, rolling updates without connection drops, and secure configuration injection. When you run Phoenix on Kubernetes, the platform’s native orchestration capabilities align perfectly with BEAM’s concurrency model, but only if you configure liveness probes, resource limits, and service discovery correctly from the start. This guide walks through the exact production patterns I use to ship resilient Phoenix applications, avoiding common pitfalls that cause silent failures in distributed environments. For teams also managing data layers, understanding PostgreSQL administration essentials is often a prerequisite for backend stability.
How do you configure a Dockerfile to run Phoenix on Kubernetes efficiently?
The foundation of any stable deployment is a reproducible, minimal container image. When preparing to run Phoenix on Kubernetes, avoid using generic Elixir images in production. Instead, use multi-stage builds to separate compilation dependencies from the runtime environment. This reduces attack surface and image size significantly, which directly impacts scaling speed and storage costs.
Multi-stage build optimization
Your Dockerfile should compile assets and dependencies in a builder stage, then copy only the release artifact to a slim runtime image. This pattern ensures no source code or build tools leak into production.
# Build stage
FROM hexpm/elixir:1.17.2-erlang-27.0-alpine-3.20.2 AS builder
RUN apk add --no-cache git npm
WORKDIR /app
ENV MIX_ENV=prod
COPY mix.exs mix.lock ./
RUN mix local.hex --force && mix deps.get --only prod
COPY assets/package.json assets/package-lock.json ./assets/
RUN npm ci --prefix assets
COPY . .
RUN npm run deploy --prefix assets
RUN mix phx.digest && mix release
# Runtime stage
FROM alpine:3.20.2 AS runtime
RUN apk add --no-cache libstdc++ openssl ncurses-libs
WORKDIR /app
COPY --from=builder /app/_build/prod/rel/my_app ./
ENV HOME=/app
USER nobody:nobody
CMD ["bin/my_app", "start"] This configuration produces an image typically under 150MB. Setting proper resource limits and requests becomes predictable when your base image size is consistent. Always run as a non-root user to satisfy security policies and reduce blast radius during incidents.
How do you manage secrets when you run Phoenix on Kubernetes?
Hardcoding credentials in ConfigMaps or baking them into Docker images is a critical security failure. In 2026, compliance frameworks like SOC 2 and ISO 27001 require strict separation of configuration and secrets. When you run Phoenix on Kubernetes, leverage native Secret objects or external secret stores to inject sensitive values at runtime.
Mounting secrets safely
Prefer mounting secrets as files rather than environment variables where possible, as env vars can leak in logs or crash dumps. However, Phoenix releases often expect env vars for DATABASE_URL and SECRET_KEY_BASE. Use Kubernetes Secrets with proper RBAC restrictions.
- Create a sealed or external secret manifest that references your vault provider.
- Define the Kubernetes Secret object with base64-encoded values or use the External Secrets Operator to sync automatically.
- Reference the secret in your Deployment spec under
envFromor individualenv.valueFromfields. - Set restrictive file permissions (0400) if mounting as volumes to prevent unauthorized process access.
For detailed implementation patterns, review Kubernetes secrets management done right. Never commit plaintext secrets to Git; use tools like SOPS, Sealed Secrets, or Vault Agent Injector to maintain audit trails and rotation capabilities.
What health checks are required to run Phoenix on Kubernetes reliably?
Kubernetes cannot distinguish between a running BEAM VM and a healthy application. Without explicit probes, traffic may route to pods stuck in initialization or deadlocked states. Defining accurate readiness and liveness probes is non-negotiable when you run Phoenix on Kubernetes in production.
Implementing probe endpoints
Create dedicated controller actions that verify downstream dependencies. A simple HTTP 200 response is insufficient; validate database connectivity and critical service availability.
# lib/my_app_web/controllers/health_controller.ex
defmodule MyAppWeb.HealthController do
use MyAppWeb, :controller
def check(conn, _params) do
case Ecto.Adapters.SQL.query(MyApp.Repo, "SELECT 1") do
{:ok, _} -> json(conn, %{status: "ok"})
{:error, reason} ->
conn |> put_status(503) |> json(%{status: "error", detail: reason})
end
end
end In your Helm values or Deployment YAML, configure probes with conservative initial delays to accommodate BEAM startup time. Set initialDelaySeconds: 15, periodSeconds: 10, and failureThreshold: 3. Avoid aggressive timeouts that trigger restart loops during garbage collection pauses. Understanding the four golden signals of monitoring helps correlate probe failures with saturation or latency issues.
How does Erlang clustering work when you run Phoenix on Kubernetes?
Elixir’s distribution mechanism relies on node discovery, which conflicts with Kubernetes’ dynamic IP assignment. Standard EPMD doesn’t resolve pod hostnames reliably. You must implement DNS-based clustering strategies to form stable clusters when you run Phoenix on Kubernetes.
DNS SRV record strategy
Use the libcluster library with the Kubernetes.DNS strategy. This queries Kubernetes DNS SRV records to discover sibling pods within the same Service. Configure your release to use this strategy via environment variables:
- Set
CLUSTER_STRATEGY=dnsin your ConfigMap. - Define a headless Service (clusterIP: None) for your Phoenix deployment to generate individual pod DNS entries.
- Ensure your Erlang cookie is identical across all pods via a shared Secret.
- Verify connectivity with
:net_adm.ping(node)from a remote shell during validation.
Without proper clustering, features like PubSub broadcasting, presence tracking, and distributed caching fail silently. Test cluster formation explicitly in staging before promoting to production.
| Clustering Strategy | Pros | Cons | Best For |
|---|---|---|---|
| Kubernetes.DNS | No extra RBAC, uses native DNS | Requires headless service | Most standard deployments |
| Kubernetes.API | Works with ClusterIP services | Needs ServiceAccount permissions | Complex network policies |
| Gossip | Zero infrastructure dependency | Unreliable in dynamic environments | Local dev only |
| Static | Predictable, simple config | Breaks on pod rescheduling | Never use in K8s |
How do you observe Phoenix applications after deploying to Kubernetes?
Running containers without telemetry is operating blind. BEAM exposes rich runtime metrics that map directly to Kubernetes observability stacks. Integrate OpenTelemetry or Prometheus exporters early to capture request latency, mailbox sizes, and memory pressure before they become outages.
Essential metrics pipeline
Configure telemetry_metrics_prometheus in your supervision tree to expose a /metrics endpoint. Scrape this with Prometheus and visualize in Grafana. Key metrics include:
- vm.memory.total: Track heap growth to tune resource limits and prevent OOM kills.
- phoenix.endpoint.stop.duration: P95/P99 latency per route to identify regressions.
- ecto.repo.query.total_time: Database performance independent of app logic.
- erlang.system.process_count: Detect process leaks before they exhaust node capacity.
Pair metrics with structured logging for correlation. Refer to structured logging best practices to ensure log lines include trace IDs and pod metadata. Without observability, debugging distributed Phoenix issues becomes guesswork.
Production Readiness Checklist for Running Phoenix on Kubernetes
Successfully operating Elixir in production demands discipline beyond initial deployment. Validate these items before considering your system production-ready:
- Multi-stage Docker builds producing minimal, non-root images.
- Secrets injected via Kubernetes Secrets or external operators, never baked in.
- Readiness and liveness probes verifying actual application health.
- DNS-based clustering configured with headless services and matching cookies.
- Resource requests and limits set based on load testing, not guesses.
- Horizontal Pod Autoscaler tuned to memory or custom BEAM metrics.
- OpenTelemetry or Prometheus metrics exposed and scraped continuously.
- Graceful shutdown hooks allowing in-flight requests to complete.
When you run Phoenix on Kubernetes with these foundations, you gain the scalability of cloud-native infrastructure without sacrificing the reliability guarantees of the BEAM. If your team needs assistance architecting compliant, observable Elixir deployments or conducting infrastructure audits, reach out to discuss your specific requirements.