Build a small chat service using Elixir and deploy it on Amazon ec2 using AWS (Last part)
Author: Tam Ly
Published: 2024-09-14

Introduction

In Part 2, we covered the implementation of chat functionality with LiveView, LiveComponent, PubSub, and Channels. In this final article, we’ll focus on compiling the application locally and deploying it to AWS EC2.

Create Dockerfile

A Dockerfile outlines a series of commands for Docker to build the application environment. By using a Dockerfile, we can create a consistent image of a test environment, which can be effortlessly and reliably recreated without the need for manual setup and configuration for each test run.

This is the sample Dockerfile .

ARG ELIXIR_VERSION=1.16.3
ARG OTP_VERSION=26.1.2
ARG DEBIAN_VERSION=bullseye-20240612-slim

ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}"
ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}"

FROM --platform=linux/amd64 ${BUILDER_IMAGE} as builder

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

# prepare build dir
WORKDIR /app
...

Run application by docker-compose

For ease of local testing, create a prod.env file with the following content:

export SECRET_KEY_BASE=<key>
export HOST=chat.vn
export PHX_HOST=chat.vn
export PHX_HTTP_PORT=8080
export PHX_HTTPS_PORT=443
export MIX_ENV=prod
export DATABASE_FILE="/data/chat_service_dev.db"
export DOCKER_DEFAULT_PLATFORM=linux/amd64

Replace <key> with own secret key by running the command mix phx.gen.secret. This prod.env file will automatically set the necessary environment variables for configuring our application.

Next, Docker Compose will help us run the application locally. Create a docker-compose.yml file in our project root with the following content:

version: '3'
services:
  elixir:
    build:
      network: host
      context: .
      dockerfile: Dockerfile
    env_file: prod.env
    environment:
      - DATABASE_FILE=/data/chat_service_dev.db
    platform: linux/amd64
    ports:
      - "4433:443"
      - "8080:8080"
    container_name: chat_service
    volumes:
      - ./data:/data
      - ./priv/cert:/cert

In this setup, Docker Compose will use the prod.env file to export the required environment variables into the running container.

To start the application, run docker-compose up or docker-compose up -d to run it in the background.

Once everything is up and running, we can open our browser and navigate to http://localhost:8080/chat_room?user_id=john&channel_id=game_1 to see the application in action.

After we will save image we just created to tar file:

docker save -o chat_service.tar <image_id> and we need to run docker image ls to get image_id.

Deploy Docker image to Amazon EC2

First, in AWS, navigate to the EC2 dashboard and launch an instance by visiting this link (https://us-east-1.console.aws.amazon.com/ec2/home?region=us-east-1#LaunchInstances).

Select Debian 12 with a 64-bit (x86) architecture, which is eligible for the free tier. This option is sufficient for our needs as our application doesn’t require a high-performance machine.

For the instance type, choose t2.micro. When setting up the Key Pair for login, create a new one and ensure we save it to our local machine. For all other settings, we can leave them as default.

Navigate to the EC2 instance dashboard to view our instances.

In the Security Groups section, we’ll see that the SSH port is pre-configured by default. Since we’ll be hosting a web app on this instance, we need to add another configuration. By default, Phoenix’s web server runs on port 8080. To allow traffic on this port, go to the Security tab and add a new Inbound rule for port 8080.

Image description

Finally, we can start our EC2 instances by command: aws ec2 start-instances --instance-ids <instance_id>.

Remember install aws command and get instance-ids name from dashboard.

Wait few seconds, check instance state in EC2 dashboard, we can see it will change status from Stopped -> Pending -> Running,

Locate your private key file. The key used to launch this instance is <name>.pem. Run this command, if necessary, to ensure your key is not publicly viewable: chmod 400 "<name>.pem".

Then we will connect to the EC2 machine by command using its Public DNS and host name is admin:

ssh -i "<name>.pem" admin@<Public IPv4 DNS>

In EC2 machine, we also need to create prod.env and docker_compose.yml file:

prod.env:

SECRET_KEY_BASE="<key>"
HOST=chat.vn
PHX_HOST=chat.vn
PHX_HTTP_PORT=8080
PHX_HTTPS_PORT=443
MIX_ENV=prod
DATABASE_FILE="/data/chat_service_dev.db"

docker_compose.yml:

version: '3'
services:
  elixir:
    image: <image_id>
    env_file: prod.env
    platform: linux/amd64
    environment:
      - DATABASE_FILE=/data/chat_service_dev.db
    ports:
      - "4433:443"
      - "8080:8080"
    container_name: chat_service
    volumes:
      - ./data:/data
      - ./priv/cert:/cert

We’ll update <image_id> in the next step. Previously, we saved the chat_service.tar file. To copy it to the EC2 instance, use the following command:

scp -i "<name>.pem"  chat_service.tar  admin@<Public IPv4 DNS>:/home/admin

Once the file is on the EC2 instance, load the image from the tar file using:

docker load -i chat_service.tar

Next, retrieve the image ID by running:

docker image ls

Update the <image_id> in your docker-compose.yml file with the ID you obtained.

Next, run docker-compose up -d to start the application. You can then access the chat service at:

<Public IPv4 address>:8080/chat_room?user_id=jack&channel_id=game_1

If there are no errors and no spinner is displayed on the LiveView page, it means the WebSocket connection has been successfully established!

It’s been a productive day! Don’t forget to terminate/stop the EC2 instance if you no longer need it to avoid incurring additional costs.

Overall Key Takeaways

  • Complete Workflow: The series provides a comprehensive guide from building a chat service with Elixir and Phoenix to deploying it on AWS EC2 using Docker.

  • Real-Time Capabilities: Emphasizes the use of Phoenix Channels for real-time communication, a crucial feature for chat applications.

  • Containerization Benefits: Highlights the benefits of using Docker for packaging and deploying applications, ensuring consistent environments and simplifying deployment.

  • Cloud Deployment: Covers the deployment process to AWS EC2, including instance configuration, security settings, and cost management.

Repo: https://github.com/ohhi-vn/embedded_chat