Elixir/Phoenix deployment patterns — Dockerfile, fly.toml, runtime.exs,
Scanned 9/5/2026
Install to Claude Code
npx -y skills add oliver-kriska/claude-elixir-phoenix --skill deploy --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Deploy?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/oliver-kriska-deploy-01638ed6)More formats (shields.io, HTML) on the badges page.
---
name: deploy
description: 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.
---
# Elixir/Phoenix Deployment Reference
Quick reference for deploying Elixir/Phoenix applications.
## Iron Laws — Never Violate These
1. **Config at runtime, not compile time** — Secrets in `config.exs` get baked into the release binary. Use `runtime.exs` with env vars so secrets are resolved at boot
2. **Graceful shutdown ≥ 60 seconds** — Shorter timeouts kill in-flight requests and WebSocket connections mid-operation, causing data loss for users
3. **Health checks required** — Without startup/liveness/readiness endpoints, orchestrators can't distinguish a booting node from a dead one, leading to cascading restarts
4. **SSL verification for database** — Skipping `verify: :verify_peer` allows MITM attacks between your app and database; production data traverses the connection
5. **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
6. **Guard optional service credentials** — `runtime.exs` runs whenever a
release boots, including `eval`-based migration commands. Only require S3,
Redis, and similar credentials when that integration is enabled
## Quick Configuration
### runtime.exs (Essential)
```elixir
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:
```elixir
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
```elixir
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: true` in 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.exs` under `:esbuild` and `:tailwind`
- `mix assets.deploy` builds for production
- `mix assets.setup` installs 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 practices
- `references/flyio-config.md` - fly.toml, clustering, commands
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!