Skip to content

Getting Started with Elixir Phoenix

A live example is available at https://phoenix.nullstone.dev.

This quickstart launches an Elixir Phoenix web application to AWS via Nullstone. It also configures a local development environment using Docker that works identical to production, but with debugging enabled.

This quickstart contains a walkthrough for generating a Phoenix app. A working example is available to fork at nullstone-io/elixir-phoenix-quickstart.

TIP

This quickstart is based off the official Phoenix Up and Running guide. Before proceeding with generation, follow the Phoenix Installation guide.

Create Phoenix app

Generate app

In this example, we are going to create a phoenix app named phoenix_quickstart in the current directory. In your repository root, run this command.

shell
mix phx.new --app=phoenix_quickstart .
mix phx.new --app=phoenix_quickstart .

This will create the following files and directories:

shell
.
├── README.md
├── assets
├── config
├── lib
├── mix.exs
├── priv
└── test
.
├── README.md
├── assets
├── config
├── lib
├── mix.exs
├── priv
└── test

Configure Datastores

A Phoenix app is not very interesting without a database. In this section, you will configure additional datastores.

Postgresql

By default, the prod environment of a Phoenix app uses DATABASE_URL environment variable to connect to a database. To work the same locally, we need to configure the application to use DATABASE_URL environment variable. Open config/dev.exs and change the database settings.

elixir
import Config

# Configure your database
config :elixir_phoenix_quickstart, ElixirPhoenixQuickstart.Repo,
  url: System.get_env("DATABASE_URL"),
#  username: "postgres",
#  password: "postgres",
#  hostname: "localhost",
#  database: "elixir_phoenix_quickstart_dev",
  stacktrace: true,
  show_sensitive_data_on_connection_error: true,
  pool_size: 10
import Config

# Configure your database
config :elixir_phoenix_quickstart, ElixirPhoenixQuickstart.Repo,
  url: System.get_env("DATABASE_URL"),
#  username: "postgres",
#  password: "postgres",
#  hostname: "localhost",
#  database: "elixir_phoenix_quickstart_dev",
  stacktrace: true,
  show_sensitive_data_on_connection_error: true,
  pool_size: 10

Prepare for Local

Configure app binding

By default, the dev environment of a Phoenix app binds to 127.0.0.1. If we tried to run inside a Docker container, we would not be able to access from our local machine. To resolve, open config/dev.exs and change the quickstart configuration to bind on 0.0.0.0.

elixir
config :elixir_phoenix_quickstart, ElixirPhoenixQuickstartWeb.Endpoint,
  # Binding to loopback ipv4 address prevents access from other machines.
  # Change to `ip: {0, 0, 0, 0}` to allow access from other machines.
  http: [ip: {0, 0, 0, 0}, port: String.to_integer(System.get_env("PORT") || "9000")],
  check_origin: false,
  code_reloader: true,
  debug_errors: true,
  secret_key_base: "yt2jRH4aPdsy4wI5q36HXKzq4GVPiWgFxJMHkl9SWbX+txl1wk82dJDGuqTQcCJq",
  watchers: [
    # Start the esbuild watcher by calling Esbuild.install_and_run(:default, args)
    esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]}
  ]
config :elixir_phoenix_quickstart, ElixirPhoenixQuickstartWeb.Endpoint,
  # Binding to loopback ipv4 address prevents access from other machines.
  # Change to `ip: {0, 0, 0, 0}` to allow access from other machines.
  http: [ip: {0, 0, 0, 0}, port: String.to_integer(System.get_env("PORT") || "9000")],
  check_origin: false,
  code_reloader: true,
  debug_errors: true,
  secret_key_base: "yt2jRH4aPdsy4wI5q36HXKzq4GVPiWgFxJMHkl9SWbX+txl1wk82dJDGuqTQcCJq",
  watchers: [
    # Start the esbuild watcher by calling Esbuild.install_and_run(:default, args)
    esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]}
  ]

Configure docker locally

TIP

Nullstone provides a docker image nullstone/phoenix:local that is configured for local development. The source for the docker image is on GitHub at nullstone-io/docker-phoenix.

Now, create a docker-compose.yml to run locally using Docker with an attached postgres Docker container.

yaml
version: "3.8"

services:
   app:
      image: nullstone/phoenix:local
      volumes:
         - .:/app
         - deps:/app/deps
      ports:
         - "9000:9000"
      environment:
         - NULLSTONE_ENV=local
         - POSTGRES_URL=postgres://postgres:postgres@db:5432/app
      depends_on:
         - db

   db:
      image: "postgres:13"
      ports:
         - "5432:5432"
      environment:
         - POSTGRES_USER=postgres
         - POSTGRES_PASSWORD=postgres
         - POSTGRES_DB=app

volumes:
   deps: {}
version: "3.8"

services:
   app:
      image: nullstone/phoenix:local
      volumes:
         - .:/app
         - deps:/app/deps
      ports:
         - "9000:9000"
      environment:
         - NULLSTONE_ENV=local
         - POSTGRES_URL=postgres://postgres:postgres@db:5432/app
      depends_on:
         - db

   db:
      image: "postgres:13"
      ports:
         - "5432:5432"
      environment:
         - POSTGRES_USER=postgres
         - POSTGRES_PASSWORD=postgres
         - POSTGRES_DB=app

volumes:
   deps: {}

Let's start our app locally.

shell
docker compose up
docker compose up

Visit http://localhost:9000.

Update dependencies

As you add dependencies to your application, ensure that there is an entry for the dependency in mix.exs. Then, restart your docker container with docker compose up or docker compose restart. The local docker image will install dependencies on boot.

Prepare for Production

Before deploying a Phoenix app to production, we need to dockerize your app. Phoenix comes with a generator to create a production Dockerfile. In this section, we will use this generator along with a base docker image to produce a production docker image.

Generate Dockerfile

Run the following command to generate a Dockerfile, .dockerignore, and release files.

shell
mix phx.gen.release --docker
mix phx.gen.release --docker

Replace final stage of docker build

The nullstone/phoenix image extends the generated Docker image to map environment variables on boot. Open Dockerfile and replace the final stage (FROM ${RUNNER_IMAGE}) with the following.

docker
FROM nullstone/phoenix
# Only copy the final release from the build stage
COPY --from=builder /app/_build/${MIX_ENV}/rel/phoenix_quickstart ./
FROM nullstone/phoenix
# Only copy the final release from the build stage
COPY --from=builder /app/_build/${MIX_ENV}/rel/phoenix_quickstart ./

Launch to Nullstone

Create app

When launching to Nullstone, we're going to create an app in the Nullstone UI and attach capabilities that automatically configure our app. Follow these steps in the Nullstone UI.

  1. Create an application.
    • Name: In this example, we're naming our app phoenix-quickstart
    • Framework: phoenix
    • App Type: Container
  2. From the Domains tab for the application, add a subdomain. (This will automatically attach a load balancer capability)
  3. From the Capabilities tab for the application, add a capability named SECRET_KEY for Rails Cookies. (This works for Rails and Phoenix and will enable secure encryption of cookies)

Create postgresql datastore

  1. Create a datastore - RDS Postgres Cluster
  2. Visit your application created in the previous step.
  3. In the Datastores tab, add the datastore you just created.

Provision

Our application is ready to launch. Click "Launch" through the UI or issue up through the CLI.

shell
nullstone up --wait --app=phoenix-quickstart --env=dev
nullstone up --wait --app=phoenix-quickstart --env=dev

Build

Once your application is provisioned, you may build and deploy your app.

You can name your image whatever you want, just remember this image name for the deploy step. In this example, we are using an image name of phoenix-app.

shell
docker build -t phoenix-app .
docker build -t phoenix-app .

Deploy

Now, issue launch to push your docker image and deploy the service with a new version.

shell
nullstone launch --source=phoenix-app --app=phoenix-quickstart --env=dev
nullstone launch --source=phoenix-app --app=phoenix-quickstart --env=dev

Troubleshooting

Auto-versioning

When pushing your image, Nullstone performs auto-versioning if you are in a git-tracked directory. Nullstone selects the short commit SHA (a unique 8-character token) from the git repository to tag the docker image.

To use a manual version, issue launch with --version (this example uses 1.0.0).

shell
nullstone launch --source=phoenix-app --app=phoenix-quickstart --env=dev --version=1.0.0
nullstone launch --source=phoenix-app --app=phoenix-quickstart --env=dev --version=1.0.0

Version conflicts

Nullstone enforces version/image tag immutability for security reasons.

If you repeatedly push a new docker image without committing anything to git, you will receive an error message like this:

shell
error pushing artifact: error pushing image: tag invalid: The image tag 'c3c7cd83' already exists in the 'periwinkle-louse-fkslv' repository and cannot be overwritten because the repository is immutable.
error pushing artifact: error pushing image: tag invalid: The image tag 'c3c7cd83' already exists in the 'periwinkle-louse-fkslv' repository and cannot be overwritten because the repository is immutable.

The easiest way to resolve this is to launch with an indexed version. The following uses the same commit sha, but with a -2 suffix to distinguish the image tag.

shell
nullstone launch --source=phoenix-app --app=phoenix-quickstart --env=dev --version=c3c7cd83-2
nullstone launch --source=phoenix-app --app=phoenix-quickstart --env=dev --version=c3c7cd83-2