Generate production-ready Docker releases for Phoenix applications.
Prerequisites
- Existing Phoenix application with Ecto (database)
- Mix installed locally
Workflow
1. Generate Release Files
Run in project root:
mix phx.gen.release --docker
This generates:
lib/<app>/release.ex- Migration and seeding helpersrel/overlays/bin/server- Server start scriptrel/overlays/bin/migrate- Migration runnerDockerfile- Multi-stage production build.dockerignore- Build exclusions
2. Configure Runtime Environment
Edit config/runtime.exs to read from environment variables:
import Config
if config_env() == :prod do
database_url =
System.get_env("DATABASE_URL") ||
raise "DATABASE_URL not set"
config :my_app, MyApp.Repo,
url: database_url,
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")
secret_key_base =
System.get_env("SECRET_KEY_BASE") ||
raise "SECRET_KEY_BASE not set"
host = System.get_env("PHX_HOST") || "example.com"
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],
secret_key_base: secret_key_base
end
3. Build Docker Image
docker build -t my_app:latest .
4. Run Container
# Generate a secret
mix phx.gen.secret
# Run with environment variables
docker run -d \
-p 4000:4000 \
-e DATABASE_URL="ecto://user:pass@host/db" \
-e SECRET_KEY_BASE="generated_secret_here" \
-e PHX_HOST="localhost" \
my_app:latest
5. Run Migrations
Before starting the app or as part of deployment:
docker run --rm \
-e DATABASE_URL="ecto://user:pass@host/db" \
my_app:latest /app/bin/migrate
Or from within the container:
/app/bin/my_app eval "MyApp.Release.migrate"
Common Customizations
Add Health Check Endpoint
Add to router for container orchestration:
get "/health", HealthController, :index
Simple controller:
defmodule MyAppWeb.HealthController do
use MyAppWeb, :controller
def index(conn, _params) do
json(conn, %{status: "ok"})
end
end
Configure DNS Clustering
For multi-node deployments, edit rel/env.sh.eex:
export RELEASE_DISTRIBUTION=name
export RELEASE_NODE=<%= @release.name %>@${HOSTNAME}
And configure in runtime.exs:
if dns_query = System.get_env("DNS_CLUSTER_QUERY") do
config :my_app, :dns_cluster_query, dns_query
end
Custom Release Commands
Add to lib/<app>/release.ex:
def seed do
load_app()
for repo <- repos() do
{:ok, _, _} = Ecto.Migrator.with_repo(repo, fn repo ->
Code.eval_file(Path.join([:code.priv_dir(:my_app), "repo", "seeds.exs"]))
end)
end
end
Environment Variables Reference
| Variable | Required | Description |
|---|---|---|
DATABASE_URL |
Yes | PostgreSQL connection string |
SECRET_KEY_BASE |
Yes | 64+ char secret (use mix phx.gen.secret) |
PHX_HOST |
No | Production hostname |
PORT |
No | HTTP port (default: 4000) |
POOL_SIZE |
No | DB pool size (default: 10) |
DNS_CLUSTER_QUERY |
No | DNS query for clustering |
Reference Files
- Dockerfile Template: See
references/dockerfile.mdfor customizing the Dockerfile