Elixir/Phoenix Deployment Reference
SkillCloud & infraElixir/Phoenix deployment patterns — Dockerfile, fly.toml, runtime.exs,
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Elixir/Phoenix Deployment Reference skill
What this skill tells your AI
The instructions your AI receives, as published by oliver-kriska/claude-elixir-phoenix in targets/pi/skills/deploy/SKILL.md and read by ahel’s review.
Quick reference for deploying Elixir/Phoenix applications.
Iron Laws — Never Violate These
- Config at runtime, not compile time — Secrets in
config.exsget baked into the release binary. Useruntime.exswith env vars so secrets are resolved at boot - 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
verify: :verify_peerallows MITM attacks between your app and database; production data traverses the connection - 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
- Guard optional service credentials —
runtime.exsruns whenever a release boots, includingeval-based migration commands. Only require S3, Redis, and similar credentials when that integration is enabled
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
Guard Optional Services
Keep core boot secrets such as DATABASE_URL and SECRET_KEY_BASE required.
Gate credentials for optional integrations behind the same feature switch that
enables the integration:
s3_config =
if System.get_env("STORAGE_BACKEND") == "s3" do
[
access_key_id:
System.get_env("S3_ACCESS_KEY") ||
raise("S3_ACCESS_KEY is required when STORAGE_BACKEND=s3"),
secret_access_key:
System.get_env("S3_SECRET_KEY") ||
raise("S3_SECRET_KEY is required when STORAGE_BACKEND=s3")
]
else
[]
end
config :my_app, :s3_config, s3_config
This lets release tasks that do not use S3 start without S3 credentials while still failing fast when S3 is selected.
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
- Optional service credentials required only when their integration is enabled
-
server: truein endpoint config - 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
config/config.exsunder:esbuildand:tailwind mix assets.deploybuilds for productionmix assets.setupinstalls binaries on first run- Custom JS bundlers: configure in
config/config.exs
References
For detailed patterns, see:
references/docker-config.md- Multi-stage Dockerfile, best practicesreferences/flyio-config.md- fly.toml, clustering, commands
Signals
- GitHub stars
- 543
- Forks
- 38
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
deploy-oliver-kriska- Source
- github.com/oliver-kriska/claude-elixir-phoenix