loading...

Deploying Lucky apps to Elastic Beanstalk

jwoertink profile image Jeremy Woertink ・3 min read

This isn't the prettiest solution, but it's what worked for us. If you have ideas on how to clean this up, I'm all for suggestions.

The setup

  • Developing locally on macOS
  • Production using Elastic Beanstalk container
  • EB container uses Docker

The concept

We had issues getting Crystal's cross compile to work properly, so we had to use a couple extra steps.

  • Boot Docker locally to compile release build of app
  • Move release binary, and required files to temp directory
  • Zip up temp directory to app.zip as per EB deployment instructions
  • Use eb command locally to push app.zip to EB container

If we happen to get cross-compilation working, we could remove the first two steps in this process.

The code

There's a few files we need to create. I'll define those first, then show the code.

  • ./script/deploy. Be sure to chmod +x ./script/deploy
  • ./docker/BuildDockerfile
  • ./docker/docker_run_build.sh
  • ./docker/Dockerfile
  • ./docker/Dockerrun.aws.json

Deploy script

Used to actually push the code to production.

#!/bin/bash

set -e

# don't execute next commands on error
trap 'exit' ERR

# let echo interpret escape chars (\n)
shopt -s xpg_echo

# 
START=`date +%s`
DATE=`date '+%Y%m%d@%H%M%S'`
BUILD_DIR=build-temp-${DATE}
SHA1=`git rev-parse HEAD`
RANDOM=`awk -v min=5 -v max=1000000 'BEGIN{srand(); print int(min+rand()*(max-min+1))}'`

# deploy to production when on master branch
# deploy to staging when on other branches
branch=$(git branch | sed -n -e 's/^\* \(.*\)/\1/p')
if [ "$branch" == "master" ]
then
  STAGE=production
else
  STAGE=staging
fi
LABEL=$STAGE-$SHA1-$RANDOM-$DATE

# Remove the old build
rm -rf build/app

# Build assets
echo "Building Assets"
rm -rf public/assets/* public/mix-manifest.json
yarn prod

# Build the application
echo "Starting Docker"
docker build -t lucky-app-build -f docker/BuildDockerfile .
docker run -v $(pwd)/build:/app/build lucky-app-build

# Create a temporary directory to stage the files
mkdir ${BUILD_DIR}

# Stage the files
cp .env.${STAGE} ${BUILD_DIR}/.env
cp build/app ${BUILD_DIR}
cp -r public ${BUILD_DIR}
cp docker/Dockerfile ${BUILD_DIR}
cp docker/Dockerrun.aws.json ${BUILD_DIR}
cp -r .ebextensions ${BUILD_DIR}

# Getting them zipped up
cd ${BUILD_DIR}; zip -Xr app.zip * .env .ebextensions; mv app.zip ../; cd ..

# Deploy the application
eb deploy --label $LABEL

# Cleanup
rm -rf ${BUILD_DIR} app.zip

END=`date +%s`

echo Deploy ended with success! Time elapsed: $((END-START)) seconds

BuildDockerfile

Used for building the release binary

FROM crystallang/crystal:0.29.0

ADD . /app
ADD ./docker/docker_run_build.sh /app/docker_run_build.sh

WORKDIR /app

RUN shards update && \
    rm -rf /app/build/app && \
    crystal build src/start_server.cr --release -o app

RUN chmod +x docker_run_build.sh

CMD ["./docker_run_build.sh"]

docker_run_build

I honestly don't know why we have this, but it's here, so here it is.

#!/bin/sh

cp app /app/build/

Dockerfile

The actual Dockerfile used in production on EB

FROM phusion/baseimage

ENV APP_DOMAIN=localhost
ENV SECRET_KEY_BASE=abc123abc123
ENV LUCKY_ENV=production
ENV PORT=8000
ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get -q update && \
    apt-get -qy install build-essential libgc-dev libssl-dev libxml2-dev libyaml-dev libevent-dev && \
    apt-get -y install tzdata && \
    apt-get -y autoremove && \
    apt-get -y clean && \
    rm -rf /var/lib/apt/lists/* && \
    rm -rf /tmp/*

RUN mkdir /app

ADD ./app /app
ADD ./.env /app/.env
ADD ./public /app/public

WORKDIR /app

EXPOSE 8000

CMD trap exit TERM; ./app & wait

Dockerrun

This file is used by EB. Learn more on Single Container Docker for AWS EB.

{
  "AWSEBDockerrunVersion": "1",
  "Logging": "/var/log/app.log",
  "Ports": [
    {
      "ContainerPort": "8000"
    }
  ]
}

Final Notes

You'll want to make sure you update your .gitignore file with things like:

/build*
*.zip
.elasticbeanstalk/*
!.elasticbeanstalk/*.cfg.yml
!.elasticbeanstalk/*.global.yml

And lastly, this all assumes you have your elastic beanstalk setup. That would require getting the eb binary installed locally, getting your container setup, and adding in all your config stuff.

Discussion

pic
Editor guide