Deploying Phoenix App to Production Using Docker Compose

In this article, we’ll break down a real-world docker-compose.yml
file used to deploy a Phoenix web application with Docker Compose, Traefik v2.9 as a reverse proxy, and environment-based configuration. This setup is designed for production with HTTPS, automated certificates via Let’s Encrypt, and health checks.
Instead of configuring the Phoenix application to terminate SSL connections, we use a proxy, Traefik, to handle all SSL connections. This enables us to use Let's Encrypt for TLS certificates. Using Traefik to manage this allows automatic renewal of certificates.
Health Check
To prepare the application for production deployment, we need to add a health check. The health check in your Docker Compose file ensures that the Phoenix application inside the main container is running and ready to receive traffic before other services (like Traefik) start routing requests to it.
The health check helps to:
- Prevent routing to broken apps: If the health check fails, Traefik (and other dependent services) will not route traffic to the container.
- Support zero-downtime deployments: It ensures the app is fully up before switching traffic, which is essential for rolling updates.
- Improve observability: You can monitor container health in Docker dashboards or orchestration tools like Docker Swarm or Kubernetes.
defmodule MyappWeb.Healthcheck do
@moduledoc """
A plug for health check, bypasses TLS rewrites.
"""
@behaviour Plug
import Plug.Conn
def init(opts), do: opts
def call(%{request_path: "/health"} = conn, _) do
conn
|> send_resp(200, "")
|> halt()
end
def call(conn, _), do: conn
end
Add the plug to the MyappWeb.Endpoint
just after the socket definition:
plug MyappWeb.Healthcheck
Mix Release
This article assumes you have configured your application as discussed in the previous blog post: Improving Your Elixir Configuration.
Elixir applications are prepared for deployment by running the MIX_ENV=prod mix release
command. This command assembles a self-contained release that can be packaged and deployed, provided the target runs the same operating system distribution and version as the machine that ran the mix release
command. The release directory includes the Erlang VM, Elixir, all code, and dependencies, which can then be deployed to the production machine.
Phoenix applications come with a Dockerfile to build Docker images. You can generate the Dockerfile by running:
mix phx.gen.release --docker
Environmental variables
To configure the deployed application we will use an .env
file that will be in the same folder as the compose.yml
.
Docker Compose File
This Compose file defines two primary services:
traefik
: Acts as the HTTPS reverse proxy and certificate manager.main
: A Phoenix application container that listens on port defined in.env
.
It also defines:
- A shared volume for storing SSL certificate data.
- A private network (
myapp_network
) connecting the services.
Traefik Service (Reverse Proxy)
services:
traefik:
image: traefik:v2.9
Traefik is configured as a standalone container that proxies HTTP(S) traffic to the Phoenix app.
Key configuration:
- Networks: Connects to
myapp_network
to route traffic to the Phoenix app. - Volumes:
/var/run/docker.sock
: Allows Traefik to discover running containers and their labels.production_traefik:/etc/traefik/acme
: Stores Let’s Encrypt TLS certificates.
- Ports:
80
for HTTP.443
for HTTPS.
- Command Flags:
- Sets up HTTP → HTTPS redirection.
- Enables automatic TLS via Let’s Encrypt.
- Uses Docker as a provider for service discovery.
Let's Encrypt
--certificatesResolvers.letsencrypt.acme.email=support@myapp.com
--certificatesResolvers.letsencrypt.acme.storage=/etc/traefik/acme/acme.json
Traefik automatically generates and renews TLS certificates using the provided email and stores them in the mounted volume.
Phoenix App Service (main)
Build the Phoenix application using the Dockerfile in the current directory:
main:
container_name: main_phoenix_app
build:
context: .
dockerfile: Dockerfile
Traefik Labels
These labels instruct Traefik how to route traffic to the Phoenix app:
- Routing Rule: Matches both the root domain and
www.
version using the${ENDPOINT_URL_HOST}
environment variable. - Middlewares: Handle CSRF headers, redirect
http://www.*
tohttps://
, and inject forwarded HTTPS headers. - TLS Settings: Enable HTTPS and link to the Let’s Encrypt resolver.
Compose File
volumes:
production_traefik: {}
services:
traefik:
image: traefik:v2.9
container_name: traefik
networks:
- myapp_network
depends_on:
- main
volumes:
- production_traefik:/etc/traefik/acme
- /var/run/docker.sock:/var/run/docker.sock
ports:
- '0.0.0.0:80:80'
- '0.0.0.0:443:443'
restart: unless-stopped
labels:
- "application=traefik"
command:
- --log.level=INFO
- --entryPoints.web.address=:80
- --entryPoints.web.http.redirections.entryPoint.to=web-secure
- --entryPoints.web.http.redirections.entryPoint.scheme=https
- --entryPoints.web-secure.address=:443
- --certificatesResolvers.letsencrypt.acme.email=support@myapp.com
- --certificatesResolvers.letsencrypt.acme.storage=/etc/traefik/acme/acme.json
- --certificatesResolvers.letsencrypt.acme.httpChallenge.entryPoint=web
- --providers.docker=true
- --providers.docker.exposedByDefault=false
main:
container_name: main_phoenix_app
build:
context: .
dockerfile: Dockerfile
env_file:
- .env
expose:
- ${PORT}
labels:
- "application=main_phoenix_app"
- "traefik.enable=true"
- "traefik.http.middlewares.csrf.headers.hostsproxyheaders=X-CSRFToken"
- "traefik.http.middlewares.redirect-https-www.redirectregex.regex=^https?://www\\.(.+)"
- "traefik.http.middlewares.redirect-https-www.redirectregex.replacement=https://$${1}"
- "traefik.http.middlewares.redirect-https-www.redirectregex.permanent=true"
- "traefik.http.middlewares.forwarded-headers.headers.customrequestheaders.X-Forwarded-Proto=https"
- "traefik.http.routers.main.rule=(Host(`${ENDPOINT_URL_HOST}`) || Host(`www.${ENDPOINT_URL_HOST}`)) && PathPrefix(`/`)"
- "traefik.http.routers.main.priority=1"
- "traefik.http.routers.main.entryPoints=web-secure"
- "traefik.http.routers.main.middlewares=redirect-https-www,csrf,forwarded-headers"
- "traefik.http.routers.main.tls.certResolver=letsencrypt"
- "traefik.http.routers.main.tls=true"
- "traefik.http.services.main.loadBalancer.server.port=${PORT}"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:${PORT}/health"]
interval: 30s
timeout: 10s
retries: 3
networks:
- myapp_network
restart: unless-stopped
networks:
myapp_network:
Conclusion
In this article, we demonstrated how to deploy a Phoenix application to production using Docker Compose and Traefik as a reverse proxy. By offloading SSL termination to Traefik and automating certificate management with Let’s Encrypt, you simplify your deployment and ensure secure HTTPS connections. Adding a health check improves reliability and supports zero-downtime deployments. This setup provides a robust foundation for deploying Phoenix apps in production environments.
To see the code changes, check out this PR
Subscribe to my newsletter
Read articles from Ambrose Mungai directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
