For a while now, I was accustomed to deploying Haskell applications by building them in a CI like CircleCI or TravisCI, then copying the resulting binaries compressed (via UPX) to S3, which were then copied over to the target servers via Ansible - a very nice and simple strategy we employed back when I worked at Zalora.
At Anapi, I have some applications that we were deploying as microservices, and I didn't really want to burn up time with deployments, so we decided to run these apps on AWS Beanstalk, which allowed for some nice features like autoscaling and relieving me of tasks like managing the load balancing etc. The workflow I configured in CircleCI is pretty simple:
- Run the usual build & test on push to Github
- If the build succeeds, then check if we are on a deployment branch, such as
- Compress binary and copy to S3 (append a hash and save it via CircleCI's workspaces
- On a deployment workflow step, copy binary from S3, and build app via Docker, push to AWS ECR
- Reload the Beanstalk environment Here's an snippet from my
version: 2 jobs: build: # ... build and test instructions here - run: stack build - run: stack test - run: mkdir -p workspace - run: echo "foo-$CIRCLE_SHA1" > workspace/s3-out - persist_to_workspace: root: workspace paths: - s3-out - run: | if [[ `git rev-parse --abbrev-ref HEAD` == "staging" ]]; then stack install --local-bin-path . /root/tools/upx foo aws s3 cp foo s3://foo-bucket/builds/foo-$CIRCLE_SHA1 fi # ... rest of build steps deploy_staging: # ... docker: - image: kenny/foo:latest steps: - checkout - run: stack setup - setup_remote_docker - run: eval $(aws ecr get-login --region ap-southeast-1 --no-include-email) - attach_workspace: at: /tmp/workspace - run: aws s3 cp s3://foo-bucket/builds/$(cat /tmp/workspace/s3-out) foo - run: docker build --rm=false -t foo.dkr.ecr.ap-southeast-1.amazonaws.com/kenny/foo:$CIRCLE_SHA1 -f Dockerfile . - run: docker push foo.dkr.ecr.ap-southeast-1.amazonaws.com/kenny/foo:$CIRCLE_SHA1 - run: aws/staging.sh $CIRCLE_SHA1 workflows: version: 2 pipeline: jobs: - build - deploy_staging: requires: - build filters: branches: only: - staging
For the Docker images I tend to start with a very minimal distro and maintain seperate images for CI and Beanstalk. With proper caching strategy, our build times vary between 3-4 mins overall on the build and testing phase, and 1-2 minutes on the deploy step, which is fairly decent.
One thing that bugs me is the CI running into OOM issues whenever a full rebuild is triggered (e.g. bumping Stackage releases), and the workaround is to run it with
stack build -j1, but it also takes around 40 minutes to complete.