Claude-elixir-phoenix deploy
Elixir/Phoenix deployment patterns — Dockerfile, fly.toml, runtime.exs, mix release, rel/ overlays. Use when configuring Fly.io, Docker, CI/CD, health checks, or production migrations.
install
source · Clone the upstream repo
git clone https://github.com/oliver-kriska/claude-elixir-phoenix
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/oliver-kriska/claude-elixir-phoenix "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/elixir-phoenix/skills/deploy" ~/.claude/skills/oliver-kriska-claude-elixir-phoenix-deploy && rm -rf "$T"
manifest:
plugins/elixir-phoenix/skills/deploy/SKILL.mdsource content
Elixir/Phoenix Deployment Reference
Quick reference for deploying Elixir/Phoenix applications.
Iron Laws — Never Violate These
- Config at runtime, not compile time — Secrets in
get baked into the release binary. Useconfig.exs
with env vars so secrets are resolved at bootruntime.exs - Graceful shutdown ≥ 60 seconds — Shorter timeouts kill in-flight requests and WebSocket connections mid-operation, causing data loss for users
- Health checks required — Without startup/liveness/readiness endpoints, orchestrators can't distinguish a booting node from a dead one, leading to cascading restarts
- SSL verification for database — Skipping
allows MITM attacks between your app and database; production data traverses the connectionverify: :verify_peer - No CPU limits — The BEAM scheduler assumes it owns all cores; cgroups CPU limits cause scheduler collapse where the VM thinks it has more cores than it can use, leading to latency spikes
Quick Configuration
runtime.exs (Essential)
if config_env() == :prod do database_url = System.get_env("DATABASE_URL") || raise "DATABASE_URL is required" secret_key_base = System.get_env("SECRET_KEY_BASE") || raise "SECRET_KEY_BASE is required" host = System.get_env("PHX_HOST") || raise "PHX_HOST is required" config :my_app, MyApp.Repo, url: database_url, pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"), ssl: true, ssl_opts: [verify: :verify_peer] config :my_app, MyAppWeb.Endpoint, url: [host: host, port: 443, scheme: "https"], http: [ip: {0, 0, 0, 0}, port: String.to_integer(System.get_env("PORT") || "4000")], secret_key_base: secret_key_base, server: true end
Health Check Plug
def call(%{path_info: ["health", "readiness"]} = conn, _opts) do case Ecto.Adapters.SQL.query(MyApp.Repo, "SELECT 1", []) do {:ok, _} -> send_resp(conn, 200, ~s({"status":"ok"})) |> halt() {:error, _} -> send_resp(conn, 503, ~s({"status":"error"})) |> halt() end end
Quick Decisions
Platform Choice
| Need | Use |
|---|---|
| Simple, managed | Fly.io |
| Enterprise, existing K8s | Kubernetes |
| Custom infrastructure | Docker + your orchestrator |
Resource Limits
| Resource | Recommendation |
|---|---|
| CPU | NO LIMITS (BEAM scheduler issues) |
| Memory | Set limits (256Mi-512Mi typical) |
| Graceful shutdown | ≥ 60 seconds |
Deployment Checklist
- All secrets from environment variables in runtime.exs
-
in endpoint configserver: true - SSL verification for database connections
- Health endpoints: /health/startup, /health/liveness, /health/readiness
- Graceful shutdown period ≥ 60 seconds
- No CPU limits (memory limits only)
- Migrations in deploy process
Asset Pipeline Notes
Phoenix 1.8 uses esbuild + tailwind (no Node.js required):
- Config in
underconfig/config.exs
and:esbuild:tailwind
builds for productionmix assets.deploy
installs binaries on first runmix assets.setup- Custom JS bundlers: configure in
config/config.exs
References
For detailed patterns, see:
- Multi-stage Dockerfile, best practices${CLAUDE_SKILL_DIR}/references/docker-config.md
- fly.toml, clustering, commands${CLAUDE_SKILL_DIR}/references/flyio-config.md