Run Phoenix on Kubernetes

Khimananda Oli 8 min read Programming and Languages
Run Phoenix on Kubernetes

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.

Ingress ControllerKubernetes Cluster (Elixir Nodes)Phoenix Pod AEPMD / DNS ClusteringSecrets MountPhoenix Pod BEPMD / DNS ClusteringSecrets MountPhoenix Pod CEPMD / DNS ClusteringSecrets MountPostgreSQL / Redis
High-level architecture to run Phoenix on Kubernetes showing ingress routing, distributed node clustering, and external state dependencies.

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.

  1. Create a sealed or external secret manifest that references your vault provider.
  2. Define the Kubernetes Secret object with base64-encoded values or use the External Secrets Operator to sync automatically.
  3. Reference the secret in your Deployment spec under envFrom or individual env.valueFrom fields.
  4. 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.

Vault / AWS SMSource of TruthExternal Secrets OpSync ControllerK8s Secret ObjectNamespace ScopedPhoenix Pod EnvAudit Log + Rotation
Secure secret injection flow ensuring credentials never touch disk or version control when deploying Elixir apps.

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=dns in 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 StrategyProsConsBest For
Kubernetes.DNSNo extra RBAC, uses native DNSRequires headless serviceMost standard deployments
Kubernetes.APIWorks with ClusterIP servicesNeeds ServiceAccount permissionsComplex network policies
GossipZero infrastructure dependencyUnreliable in dynamic environmentsLocal dev only
StaticPredictable, simple configBreaks on pod reschedulingNever use in K8s
DNS Strategy (Recommended)Pod APod BPod CHeadless Service → SRV RecordsAPI Strategy (Fallback)Pod XPod YK8s APIRequires RBAC + Polling
Visual comparison of DNS versus API clustering topologies highlighting trade-offs for distributed Elixir systems.

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:

  1. Multi-stage Docker builds producing minimal, non-root images.
  2. Secrets injected via Kubernetes Secrets or external operators, never baked in.
  3. Readiness and liveness probes verifying actual application health.
  4. DNS-based clustering configured with headless services and matching cookies.
  5. Resource requests and limits set based on load testing, not guesses.
  6. Horizontal Pod Autoscaler tuned to memory or custom BEAM metrics.
  7. OpenTelemetry or Prometheus metrics exposed and scraped continuously.
  8. 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.

Frequently Asked Questions

Phoenix requires Kubernetes 1.32 or later for stable Gateway API support and improved container runtime compatibility.

Use Kubernetes Secrets mounted as environment variables or files, referencing PostgreSQL connection strings with proper pooling via PgBouncer sidecars.

Yes, use an Ingress controller like NGINX or Traefik to route external traffic to your Phoenix service internally.

Start with 500m CPU and 512Mi memory requests, adjusting based on observed usage during load testing with kubectl top.

Configure readiness probes and preStop hooks to ensure zero-downtime deployments during standard Kubernetes rolling update strategies.

Often yes for sustained workloads, though savings depend on cluster utilization, team expertise, and avoiding over-provisioned node pools.

Create a custom Helm chart templating Phoenix config, secrets, and ingress rules for repeatable, version-controlled deployments across environments.

Use ReadWriteOnce block storage like EBS or Ceph RBD for local uploads, or S3-compatible object storage via libcluster for distributed setups.

Check pod logs with kubectl logs, verify configmap mounts, and validate environment variable injection using kubectl exec for interactive inspection.

No, but enable DNS-based service discovery and consider NetworkPolicies to restrict inter-pod communication for security compliance.

Use HorizontalPodAutoscaler targeting CPU or custom metrics from Prometheus, ensuring session state is externalized to Redis or database.

Terminate TLS at the Ingress controller level using cert-manager for automated certificate provisioning and renewal.

Deploy OpenTelemetry collectors as DaemonSets and export traces to Grafana Tempo or Jaeger for distributed request visibility.

Yes, use ExternalName services or sealed secrets for credentials, avoiding hardcoded endpoints in application configuration files.

Missing health checks, improper secret management, insufficient resource requests causing OOMKills, and neglecting graceful shutdown handling in release configs.