Deploy Elixir on Temps

Choose the Autopack (Elixir) preset (nixpacks-elixir) and run up: Temps builds a mix release (with mix assets.deploy for Phoenix) and starts it on $PORT behind automatic HTTPS, so no Dockerfile is needed. Set PHX_SERVER, read POSTGRES_URL for a linked database, and run Ecto migrations yourself after schema changes.

This guide describes how the nightly release channel builds your app, using the Autopack builder. Beta (v0.1.0-beta.56) also builds with Autopack, but some details below may differ. Earlier releases and the stable channel build with Nixpacks and behave differently. To run nightly, see Release channels.

Every framework and the preset it needs is listed under Supported platforms. The Rails and Django guides follow the same pattern for database-backed apps.


Quickstart

From your project root, deploy with your preferred package manager:

npx @temps-sdk/cli up --preset nixpacks-elixir

Temps doesn't detect Elixir projects from mix.exs, so choose the Autopack (Elixir) preset (nixpacks-elixir), as above or under Project → Settings → Build & deploy → Build → Framework.

With that preset, Temps runs mix deps.get --only prod, mix compile, mix assets.deploy (for Phoenix apps with an assets/ directory) and mix release, then starts the release with bin/<app> start on a Debian slim image. The Elixir version comes from the elixir: requirement in mix.exs, and defaults to 1.18. Temps sets MIX_ENV=prod for you.

Phoenix

A Phoenix release only starts its web server when PHX_SERVER is set, and Phoenix's generated config/runtime.exs reads SECRET_KEY_BASE and DATABASE_URL. Set the first two as project variables:

npx @temps-sdk/cli environments vars set PHX_SERVER true -e production

npx @temps-sdk/cli environments vars set SECRET_KEY_BASE "$(mix phx.gen.secret)" -e production

A linked managed PostgreSQL database is injected as POSTGRES_URL, not DATABASE_URL, so change config/runtime.exs to read it:

config/runtime.exs

database_url =
  System.get_env("POSTGRES_URL") ||
    raise "environment variable POSTGRES_URL is missing."

What Temps handles automatically

FeatureHow Temps handles it
DetectionChoose the Autopack (Elixir) preset (nixpacks-elixir)
Buildmix release, plus mix assets.deploy for Phoenix apps with assets
HTTPSLet's Encrypt certificate, auto-renewed
PortPORT env var injected
Health checksHTTP check on / at deploy time, or the path set in .temps.yaml
Zero-downtime deploysNew container starts before old one stops

Database migrations are not run automatically — see Run migrations below.


Run migrations

A release doesn't include Mix, so mix ecto.migrate isn't available in the container. Generate a release module that runs the migrations instead:

mix phx.gen.release

This adds MyApp.Release with a migrate/0 function. Temps writes the release to /app/release, so after a deploy that changes the schema, find the container and run it over SSH:

bunx @temps-sdk/cli projects show -p my-phoenix-app --json   # note the numeric "id"
bunx @temps-sdk/cli containers list -p <project-id>          # note the container_name
ssh your-server 'docker exec <container_name> /app/release/bin/my_app eval "MyApp.Release.migrate"'

Run this manually or from your CI/CD pipeline right after the deploy step. Don't add the migration to the start command or a Procfile web: line: with rolling deploys, restarts or several replicas it can run more than once at the same time against the same database.


Custom Dockerfile (optional)

If you want full control over the release build, add your own Dockerfile. For example, a multi-stage build on Alpine:

Dockerfile (Phoenix)

FROM elixir:1.18-alpine AS build
RUN apk add --no-cache build-base git

WORKDIR /app
ENV MIX_ENV=prod

COPY mix.exs mix.lock ./
RUN mix deps.get --only prod && mix deps.compile

COPY lib ./lib
COPY priv ./priv
COPY config ./config
RUN mix compile && mix release

# Use the same Alpine release as the build image
FROM alpine:3.22
RUN apk add --no-cache libstdc++ openssl ncurses-libs

WORKDIR /app
COPY --from=build /app/_build/prod/rel/my_app ./

ENV PORT=4000
EXPOSE 4000
CMD ["bin/my_app", "start"]

Because this Dockerfile listens on 4000, set the project's port to 4000 under Project → Settings → Build & deploy → Deploy, so the PORT Temps sets matches the port it routes to. Make sure your Phoenix endpoint reads the port from the environment and starts its server:

config/runtime.exs

config :my_app, MyAppWeb.Endpoint,
  url: [host: System.get_env("APP_HOST") || "localhost"],
  http: [ip: {0, 0, 0, 0}, port: String.to_integer(System.get_env("PORT") || "4000")],
  server: true

A Dockerfile in your repository root always takes priority over auto-detection. See the Custom Dockerfile guide for build caching and more.


Platform behavior

These rules apply to every app deployed on Temps, regardless of framework.

The one requirement: your app must listen on the port in the PORT environment variable and bind to 0.0.0.0 — not localhost or 127.0.0.1. Temps runs your app in a container and routes traffic from the host, so an app bound to localhost only accepts connections from inside the container and will fail its health check.

Health checks

After your container starts, Temps sends HTTP GET requests to verify it is healthy before routing traffic to it.

  • Path: / (the root of your application) by default
  • Success: 2 consecutive responses with a 2xx, 3xx, 404 or 405 status code
  • Timeout: 300 seconds (5 minutes) for the app to become healthy, fixed in v0.1.0-beta.56. If the app answers with error responses (other 4xx, or 5xx) for 60 seconds in a row, the deployment fails sooner. From the next release the timeout is configurable from 30 to 3600 seconds under Project → Settings → Build & deploy
  • Retry interval: 2 seconds between successful checks, 5 seconds after an error response or a failed connection

Connection errors and other 4xx or 5xx responses are retried until the timeout; if the app hasn't passed by then, the deployment fails. The check runs once, at deploy time. To check a different path, add a .temps.yaml to your repository root:

.temps.yaml
health:
  path: /health

Only path is used. Other health fields (status, interval, timeout, retries) are accepted but currently have no effect.

Add a dedicated /health endpoint that returns a simple 200. This avoids issues where / requires authentication or returns a redirect.

Auto-injected environment variables

Temps injects these variables into every deployment automatically:

VariableValueDescription
PORTResolved portThe port your app must listen on
HOST0.0.0.0Bind address
SENTRY_DSNAuto-generatedError tracking endpoint
TEMPS_API_URLYour Temps URLPlatform API endpoint
TEMPS_API_TOKENDeployment tokenAuthentication for Temps SDKs
OTEL_EXPORTER_OTLP_ENDPOINTYour Temps OTLP URLOpenTelemetry trace collection
OTEL_SERVICE_NAMEProject nameService identifier for traces

You do not need to configure these manually. They are available in process.env (Node.js), os.environ (Python), os.Getenv (Go), and the equivalent in other languages.


Next Steps

Last updated

Was this page helpful?