Deploying Lucky apps to Elastic Beanstalk
Tags: lucky, crystal
Published: 2019-06-29 17:09:58

---
title: Deploying Lucky apps to Elastic Beanstalk
published: true
description: Guide to deploying Lucky apps to AWS Elastic Beanstalk
tags: lucky, crystal
---

> 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](https://aws.amazon.com/elasticbeanstalk/) 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.

```bash
#!/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](https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/single-container-docker-configuration.html) for AWS EB.

```json
{
  "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.