Deploying Elixir/Phoenix/Ecto/Postgres Project at AWS EC2
A short summary how to deploy a Phoenix-Project to an AWS-Instance
Sure, there are deploy-utils out there which can automate deploying taking every single detail into consideration. Though, for a simple project, simple shell scripts can do the job as well.
Ingredients
- Elixir / Phoenix to create your project
- Docker and Docker-Compose to create your docker-images
- An account at docker-hub and Amazon-AWS
- Some simple shell-scripts
The Goal
I want to be able to compile and deploy a Phoenix-project with a single command from my workstation.
How to
- Create your phoenix project. See: Phoenix
- Write a
Dockerfile
. See below. - Write a
docker-compose.yml
file. See below. - A simple script is needed to set the proper permissions for
- postgres, thus the application container has access to the
postgres-server in the postgres.
See
initialize_database.sh
below. - Create an EC2-instance using AWS-Linux on Amazon’s platform.
- Install Docker and Docker-compose
- Run the script
build-and-deploy
as shown below for the first time. Login to EC2 and run the following commands once:
docker-compose down docker-compose up init # Will initialize the database docker-compose up -d web # Will start the app
From now on you can use the script build-and-deploy
-script.
This blog post doesn’t cover how to create your EC2-instance and how to install “Docker” and “Docker-compose” but it shouldn’t be a big deal.
- Create an instance using “AWS-Linux”
- install docker and docker-compose
- Log in to your docker-hub account
Just a hint. Google for ‘install docker-compose on aws-linux’ if it doesn’t work as simple as
sudo bash
yum install -y docker
sudo yum install -y docker-
Files
The Dockerfile
We’re using the latest postgres-image from docker-hub and build our
application image from this Dockerfile
. Notice the last line of
the file is needed only if you use bcrypt
within your project.
FROM elixir:1.5.1
ENV DEBIAN_FRONTEND=noninteractive
MAINTAINER Your Name
RUN curl -sL https://deb.nodesource.com/setup_8.x | bash -
RUN apt-get install -y -q nodejs gcc
# Suggested https://hexdocs.pm/phoenix/installation.html
RUN apt-get update && apt-get install -y \
inotify-tools \
&& rm -rf /var/lib/apt/lists/*
# Create and set home directory
ENV HOME /opt/app
WORKDIR $HOME
# done by docker-compose
# EXPOSE 4000
# Configure required environment
ENV MIX_ENV prod
# Install hex (Elixir package manager)
RUN mix local.hex --force
# Install rebar (Erlang build tool)
RUN mix local.rebar --force
# Copy all dependencies files
COPY mix.* ./
# Install all production dependencies
RUN mix deps.get --only prod
# Compile all dependencies
RUN mix deps.compile
# Copy all application files
COPY . .
# Compile the entire project and build the digest for production
RUN mix do compile, phx.digest
RUN (cd deps/bcrypt_elixir && make clean && make)
docker-compose.yml
Replace your-docker-hub-accout/your-docker-image-name:latest
with the names for your account and project. For exapmple
nickname/project-one:latest
.
init
This service will be executed one to create the database with docker-compose up init
.
web
Starts the database and elixir app. docker-compose up -d web
will do the job and -d
will keep it running in background.
postgres
Uses the official postgres docker image.
version: '3'
services:
init:
image: "your-docker-hub-accout/your-docker-image-name:latest"
environment:
- MIX_ENV=prod
- PORT=4000
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_HOST=postgres
volumes:
- "./postgresql:/opt/app/postgresql"
command: ./initialize_database.sh
networks:
- backend
depends_on:
- postgres
web:
image: "your-docker-hub-account/your-docker-image-name:latest"
ports:
- "80:4000"
command: mix do ecto.migrate, phx.digest, phx.server
environment:
- MIX_ENV=prod
- MIX_HOST=localhost
- LMS_HOST=localhost
- PORT=4000
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_HOST=postgres
networks:
- backend
- frontend
depends_on:
- postgres
postgres:
image: postgres:latest
volumes:
- "./postgresql/data:/var/lib/postgresql/data"
ports:
- "5432:5432"
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_HOST=postgres
networks:
- backend
networks:
frontend:
backend:
initialize_database.sh
#!/bin/bash
echo "TRUST ALL LOCAL CONNECTIONS IN pg_hba.conf"
if test -d ./postgresql
then
echo "host all all all trust" >> ./postgresql/data/pg_hba.conf
echo "WAIT FOR POSTGRES RECOGNIZING NEW PERMISSIONS"
sleep 10
mix do ecto.create, ecto.migrate
echo ""
echo ""
echo "IF ANY ERROR OCCURED RUN 'docker-deploy up init' AGAIN"
echo "OTHERWISE YOU CAN START THE CONTAINER WITH:"
echo ""
echo " docker-deploy up -d web "
else
echo "DATABASE NOT CREATED YET. PLEASE TRY AGAIN"
fi
Shell scripts to build and deploy
build-and-deploy
is the one and only script you have to call and
should be located in the root-path of your project. For all other
scripts create a subfolder deploy
.
build-and-deploy.sh
#!/bin/bash
docker build -t your-docker-hub-account/your-docker-image-name:latest .
docker push your-docker-hub-account/your-docker-image-name:latest
deploy/deploy-aws.sh
In subfolder deploy
deploy-aws.sh will run localy
You will have to replace XX-XX-XXX-XXX.eu-central-1
with the address of your EC2-server.
and path/to/your/private-aws.pem
with the path to your local private key (you will get when
creating your EC2 instance)
#!/bin/bash
echo "********************************************************"
echo "COPY UPDATE SCRIPTS TO AWS-INSTANCE"
echo "********************************************************"
scp -i ~/path/to/your/private-aws.pem deploy/update-and-restart.sh \
deploy/restart-docker.sh \
ec2-user@ec2-XX-XX-XXX-XXX.eu-central-1.compute.amazonaws.com:./
echo "********************************************************"
echo "UPDATE AWS-INSTANCE"
echo "********************************************************"
ssh -i ~/path/to/your/private-aws.pem \
ec2-user@ec2-XX-XX-XXX-XXX.eu-central-1.compute.amazonaws.com \
sudo ./update-and-restart.sh
echo "********************************************************"
echo "DEPLOYED `date`"
echo "********************************************************"
restart-docker.sh will run on EC2-instance
#!/bin/bash
echo "********************************************************"
echo "DOWNLOAD CURRENT IMAGE "
echo "********************************************************"
sudo docker pull your-docker-hub-account/your-docker-image-name:latest > /dev/null
echo "********************************************************"
echo "RESTART DB AND SERVICE WEB "
echo "********************************************************"
cd /home/ec2-user/your-app-dir && \
/usr/local/bin/docker-compose down
cd /home/ec2-user/your-app-dir && \
/usr/local/bin/docker-compose up -d web
sleep 5
echo "********************************************************"
echo "DOCKER IMAGE UPDATED AND SERVICE RESTARTED "
echo "********************************************************"
update-and-restart.sh will run on EC2-instance
#!/bin/bash
echo "********************************************************"
echo "UPDATING AND RESTARTING "
echo "********************************************************"
hostname
pwd
echo "--------------------------------------------------------"
sudo docker pull your-docker-hub-account/your-docker-image-name:latest
sudo /home/ec2-user/restart-docker.sh
sudo docker system prune -f
echo "---------- UPDATE DONE --------------------------------"