Curated library of 38 atomic skills, 7 personas, and 1 orchestrator for Elixir and Phoenix development. Organized by category: fundamentals, phoenix, database, testing, auth, infrastructure, quality, security, integrations, tooling, frameworks, personas, and orchestration. Covers core Elixir patterns, Phoenix LiveView, Ecto, OTP, Oban, testing, security, deployment, real-time, and modern tooling (Req, Swoosh, Cachex, Broadway, Ash).
73
91%
Does it follow best practices?
Impact
—
No eval scenarios have been run
Advisory
Suggest reviewing before use
Use this skill before modifying ANY deployment or release configuration.
runtime.exs for all secrets and URLs; never hardcode secrets — use System.get_env!/1 — see §1 & §5bin/migrate) — see §2PHX_HOST and PHX_SERVER=true — see §3mix assets.deploy before building the release — see §4/health endpoint that queries the database — see §6config :logger, level: :info in production — see §71. Build image (mix assets.deploy → mix release → docker build)
2. Run migrations (bin/my_app eval "MyApp.Release.migrate()")
3. Start app (bin/my_app start)
4. Verify /health returns HTTP 200 and {"database": "connected"}
5. If health check fails → rollback: bin/my_app eval "MyApp.Release.rollback(MyApp.Repo, <version>)"❌ Bad — compiled into release, cannot read env vars at boot:
# config/prod.exs
config :my_app, MyApp.Repo,
url: System.get_env("DATABASE_URL") # Always nil in release!✅ Good — evaluated at boot, reads env vars correctly:
# config/runtime.exs
if config_env() == :prod do
database_url = System.get_env!("DATABASE_URL")
config :my_app, MyApp.Repo,
url: database_url,
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")
end✅ Good — release module for migrations:
# lib/my_app/release.ex
defmodule MyApp.Release do
@app :my_app
def migrate do
load_app()
for repo <- repos() do
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
end
end
def rollback(repo, version) do
load_app()
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version))
end
defp repos, do: Application.fetch_env!(@app, :ecto_repos)
defp load_app do
Application.ensure_all_started(:ssl)
Application.load(@app)
end
end# Run migrations in production
bin/my_app eval "MyApp.Release.migrate()"✅ Good:
# config/runtime.exs
if config_env() == :prod do
host = System.get_env!("PHX_HOST")
port = String.to_integer(System.get_env("PORT") || "4000")
config :my_app, MyAppWeb.Endpoint,
url: [host: host, port: 443, scheme: "https"],
http: [ip: {0, 0, 0, 0}, port: port],
server: true
end✅ Good — correct Dockerfile order:
# Multi-stage build
FROM elixir:1.16-alpine AS build
RUN apk add --no-cache build-base git
WORKDIR /app
RUN mix local.hex --force && mix local.rebar --force
ENV MIX_ENV=prod
RUN mix deps.get --only prod
RUN mix deps.compile
RUN mix assets.deploy
RUN mix release
# Runtime stage
FROM alpine:3.18 AS app
RUN apk add --no-cache libstdc++ openssl ncurses
WORKDIR /app
COPY --from=build /app/_build/prod/rel/my_app ./
ENV HOME=/app
CMD ["bin/my_app", "start"]Always use System.get_env!/1 in runtime.exs so the app crashes on startup if a required secret is missing rather than silently misconfiguring.
✅ Good — read from environment, crash on startup if missing:
# config/runtime.exs
if config_env() == :prod do
secret_key_base = System.get_env!("SECRET_KEY_BASE")
config :my_app, MyAppWeb.Endpoint,
secret_key_base: secret_key_base
end# Generate a secret
mix phx.gen.secret✅ Good — queries the database:
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", database: "connected"})
{:error, reason} ->
conn
|> put_status(:service_unavailable)
|> json(%{status: "error", database: inspect(reason)})
end
end
end✅ Good:
# config/prod.exs
config :logger, level: :info
# config/runtime.exs — allow override for debugging
if config_env() == :prod do
log_level =
case System.get_env("LOG_LEVEL") do
"debug" -> :debug
"warning" -> :warning
"error" -> :error
_ -> :info
end
config :logger, level: log_level
end| Predecessor | This Skill | Successor |
|---|---|---|
| telemetry-essentials | deployment-gotchas | None (standalone) |