> ## Documentation Index
> Fetch the complete documentation index at: https://docs.localops.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Elixir/Phoenix

> Step-by-step guide to setting up a Dockerfile for an Elixir/Phoenix application (with an Erlang/Cowboy variant) and deploying it via LocalOps.

This documentation provides a step-by-step guide to setting up a Dockerfile for a web application written using
[Phoenix][phoenix] and [Elixir][elixir]. An [Erlang][erlang] variant using [Cowboy][cowboy] and `rebar3` is included at
the end.

## Prerequisites

To follow this tutorial, you will need the following:

* [Elixir][elixir] (v1.17 or later at the time of writing this doc) and the matching [Erlang/OTP][erlang] (OTP 27 or
  later) installed via [`asdf`](https://asdf-vm.com) or your platform package manager.
* [Mix][mix] as the build tool for Elixir. This comes bundled with Elixir.
* [Phoenix installer][phoenix-install] (`mix archive.install hex phx_new`).
* A basic knowledge of [Elixir][elixir] and [Phoenix][phoenix].
* [Docker] to build standalone containers to serve your production app.

<Note>
  This guide assumes that you have basic knowledge of the above-mentioned technologies and tools. If you are not
  familiar with any of them, it is highly recommended to review their official documentation and tutorials to get up to
  speed before proceeding with this guide.
</Note>

## Scaffolding the Phoenix app

Create a new Phoenix project. In the following examples, we'll use the project name `hello_app`. The `--no-ecto` flag
skips database setup so we can focus on the Dockerfile — drop it if your real app uses a database.

```bash theme={null}
mix phx.new hello_app --no-ecto
cd hello_app
mix deps.get
```

### Hello World! example

Open `lib/hello_app_web/router.ex` and add a simple route at `/` returning JSON:

```elixir lib/hello_app_web/router.ex theme={null}
defmodule HelloAppWeb.Router do
  use HelloAppWeb, :router

  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/", HelloAppWeb do
    pipe_through :api

    get "/", PageController, :hello
    get "/health", PageController, :health
  end
end
```

Add the controller at `lib/hello_app_web/controllers/page_controller.ex`:

```elixir lib/hello_app_web/controllers/page_controller.ex theme={null}
defmodule HelloAppWeb.PageController do
  use HelloAppWeb, :controller

  def hello(conn, _params) do
    json(conn, %{message: "Hello, World!"})
  end

  def health(conn, _params) do
    json(conn, %{status: "healthy"})
  end
end
```

Run the application locally to verify it works:

```bash theme={null}
mix phx.server
```

Open `http://localhost:4000` in your browser. You should see `{"message":"Hello, World!"}`.

## Create the Docker image

Dockerizing makes the app run anywhere, agnostic of the platform. As long as Docker is installed, whether it's Windows,
Mac, or Linux, it can run with the same behavior.

The recommended way to ship a Phoenix app in production is as an [Elixir release][release] — a self-contained tarball
that bundles the Erlang VM, your compiled code, and all dependencies. It's smaller and faster to boot than running
`mix phx.server` in production.

### Generate the release config

If you haven't already, generate the release config that Phoenix uses to start your app inside a release:

```bash theme={null}
mix phx.gen.release --docker
```

This creates `rel/overlays/bin/server`, `rel/overlays/bin/migrate`, and a starter `Dockerfile` and `.dockerignore` tuned
for Phoenix releases. You can use the generated files as-is or customize them with the snippets below.

### Create [.dockerignore](https://docs.docker.com/build/building/context/#dockerignore-files)

Before building the image, create a `.dockerignore` file with paths that shouldn't be copied into the build context:

```ignore .dockerignore theme={null}
_build
deps
.elixir_ls
.git
.gitignore
Dockerfile
.dockerignore
README.md
test
priv/static
node_modules
```

Read more about [.dockerignore here](https://docs.docker.com/build/building/context/#dockerignore-files).

<Note>
  Excluding `_build`, `deps`, and `node_modules` is important — these contain build artifacts from your local machine
  that may not match the target platform. The build will happen inside the Docker container with the correct platform
  settings.
</Note>

### Create Dockerfile

Now, create a [Dockerfile](https://docs.docker.com/reference/dockerfile/). The Dockerfile uses
[Multi-Stage Builds](https://docs.docker.com/build/building/multi-stage/) — a builder stage with the full Elixir
toolchain compiles the release, and a tiny Debian slim runtime stage runs it. The final image is typically under 100 MB.

```docker Dockerfile theme={null}
# Build stage - uses the official Elixir image with build tools
FROM hexpm/elixir:1.17.3-erlang-27.1.2-debian-bookworm-20241016-slim AS build

# Install build dependencies
RUN apt-get update -y && \
    apt-get install -y build-essential git curl && \
    apt-get clean && rm -rf /var/lib/apt/lists/*

WORKDIR /app

# Install hex + rebar
RUN mix local.hex --force && mix local.rebar --force

# Set build environment
ENV MIX_ENV=prod

# Copy mix files and fetch deps
COPY mix.exs mix.lock ./
COPY config config
RUN mix deps.get --only prod
RUN mix deps.compile

# Copy assets and compile them
COPY assets assets
COPY priv priv
RUN mix assets.deploy

# Copy source and compile the app
COPY lib lib
RUN mix compile

# Build the release
COPY rel rel
RUN mix release

# Runtime stage - uses a minimal Debian image
FROM debian:bookworm-slim AS runtime

# Install runtime dependencies for the BEAM VM
RUN apt-get update -y && \
    apt-get install -y libstdc++6 openssl libncurses5 locales ca-certificates && \
    apt-get clean && rm -rf /var/lib/apt/lists/*

# Set the locale
RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen
ENV LANG=en_US.UTF-8 LANGUAGE=en_US:en LC_ALL=en_US.UTF-8

WORKDIR /app
RUN chown nobody /app

# Copy the release from the build stage
COPY --from=build --chown=nobody:root /app/_build/prod/rel/hello_app ./

USER nobody

# Default Phoenix port
ENV PORT=4000
ENV PHX_SERVER=true
EXPOSE 4000

ENTRYPOINT ["/app/bin/server"]
```

<Info>
  Pin the Elixir, Erlang, and Debian versions in the `hexpm/elixir` tag to match what your team develops against — a
  mismatch between local and container OTP versions is the most common source of "works on my machine" surprises with
  the BEAM.
</Info>

### Build Docker image

Now you can build the Docker image:

```bash theme={null}
docker build --platform linux/amd64 -t hello-app:latest .
```

This command builds the `hello-app` image for platform `linux/amd64` and tags it as `latest`.

If you are locally testing your application, you can skip the `platform` key:

```bash theme={null}
# only for testing on local machine
docker build -t hello-app:latest .
```

### Run the Docker image

Let's run the Docker container using the image created of the Phoenix application. The app needs a `SECRET_KEY_BASE` to
start in production — generate one with `mix phx.gen.secret` and pass it via `-e`:

```bash theme={null}
docker run \
  -it \
  --rm \
  --name hello-app \
  -e PORT=4000 \
  -e PHX_SERVER=true \
  -e SECRET_KEY_BASE=$(mix phx.gen.secret) \
  -d \
  -p 4000:4000 \
  hello-app:latest
```

* `-it`: enables interactivity with TTY.
* `--rm`: tells the Docker Daemon to clean up the container and remove the file system after the container exits.
* `--name hello-app`: Name of the container `hello-app`.
* `-e PORT=4000`: Sets the HTTP port Phoenix listens on.
* `-e PHX_SERVER=true`: Tells the release to start the Phoenix endpoint (releases skip it by default).
* `-e SECRET_KEY_BASE=...`: Required signing/encryption secret for sessions and tokens.
* `-d`: Runs the container in detached (background) mode.
* `-p 4000:4000`: Maps port 4000 on your host to port 4000 in the container.

After running the command, visit `http://localhost:4000` to see the Phoenix application running inside the Docker
container.

To view logs from the container:

```bash theme={null}
docker logs hello-app
```

To stop and remove the container:

```bash theme={null}
docker stop hello-app
```

## Erlang variant (Cowboy + rebar3)

For Erlang services using [Cowboy][cowboy] directly, the structure is similar — replace Mix with `rebar3` and the Elixir
release with an Erlang relx-based release.

A minimal `rebar.config`:

```erlang rebar.config theme={null}
{erl_opts, [debug_info]}.
{deps, [
    {cowboy, "2.12.0"}
]}.

{relx, [
    {release, {hello_app, "0.1.0"}, [hello_app, sasl]},
    {dev_mode, false},
    {include_erts, true},
    {extended_start_script, true}
]}.

{profiles, [
    {prod, [{relx, [{dev_mode, false}, {include_erts, true}]}]}
]}.
```

The Dockerfile shape is the same — a builder stage compiles the release, a slim runtime stage runs it:

```docker Dockerfile theme={null}
FROM erlang:27-slim AS build

WORKDIR /app
COPY rebar.config rebar.lock ./
RUN rebar3 get-deps
COPY src src
RUN rebar3 as prod release

FROM debian:bookworm-slim AS runtime
RUN apt-get update -y && \
    apt-get install -y libstdc++6 openssl libncurses5 ca-certificates && \
    apt-get clean && rm -rf /var/lib/apt/lists/*

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

ENV PORT=8080
EXPOSE 8080

ENTRYPOINT ["/app/bin/hello_app", "foreground"]
```

Build and run with the same `docker build` / `docker run` commands as above, swapping the image name.

## Done 🎉

You can now commit and push the Dockerfile to your git repo. [Create a service](/environment/services/create) now to
point at the git repository and branch name to deploy this image.

To wire up Prometheus metrics for your Phoenix or Erlang service after deploying, see the
[Erlang & Elixir instrumentation guide](/environment/services/instrument/erlang).

If your nodes need to find each other over distributed Erlang — for Phoenix PubSub, a distributed registry, or handing
work to a peer — see [BEAM clustering](/environment/services/beam-clustering).

[elixir]: https://elixir-lang.org

[erlang]: https://www.erlang.org

[phoenix]: https://www.phoenixframework.org

[phoenix-install]: https://hexdocs.pm/phoenix/installation.html

[mix]: https://hexdocs.pm/mix/Mix.html

[cowboy]: https://github.com/ninenines/cowboy

[release]: https://hexdocs.pm/phoenix/releases.html

[Docker]: https://www.docker.com/
