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).
91
91%
Does it follow best practices?
Impact
91%
1.37xAverage score across 56 eval scenarios
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 the health check fails, an operator reviews the failure and runs the rollback
manually: 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.secretThe health check below is a read-only liveness probe: it runs a single SELECT 1
to confirm the database connection is alive and reports the result. It does not write,
migrate, or delete anything — it only informs an external load balancer or operator
whether the release is ready to serve traffic.
✅ 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| ❌ Don't | ✅ Do |
|---|---|
Read env vars in config/prod.exs (compiled) | Read them in config/runtime.exs (evaluated at boot) |
System.get_env("SECRET") that returns nil silently | System.get_env!("SECRET") so the release crashes on startup |
Run mix ecto.migrate against a release | Run bin/my_app eval "MyApp.Release.migrate()" |
Forget PHX_SERVER=true and get no HTTP server | Set server: true / PHX_SERVER=true in runtime config |
Build the release before mix assets.deploy | Run mix assets.deploy first, then mix release |
Ship a /health that returns 200 without checking the DB | Run a read-only SELECT 1 liveness check in the health endpoint |
Leave :debug logging on in prod (leaks PII/params) | Use config :logger, level: :info in production |
| Predecessor | This Skill | Successor |
|---|---|---|
| telemetry-essentials | deployment-gotchas | None (standalone) |
.tessl-plugin
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10
scenario-11
scenario-12
scenario-13
scenario-14
scenario-15
scenario-16
scenario-17
scenario-18
scenario-19
scenario-20
scenario-21
scenario-22
scenario-23
scenario-24
scenario-25
scenario-26
scenario-27
scenario-28
scenario-29
scenario-30
scenario-31
scenario-32
scenario-33
scenario-34
scenario-35
scenario-36
scenario-37
scenario-38
scenario-39
scenario-40
scenario-41
scenario-42
scenario-43
scenario-44
scenario-45
scenario-46
scenario-47
scenario-48
scenario-49
scenario-50
scenario-51
scenario-52
scenario-53
scenario-54
scenario-55
scenario-56
skills
frameworks
ash-framework
infrastructure
orchestration
elixir-skill-router
personas
phoenix
quality
security
security-essentials
tooling
mix-tasks-generators