This is one of the first times I’ve used make, but I regret not using it more often. It’s pretty simple to get started and really powerful. This was a mix of a lot of different resources.

This is part of a larger set of posts, see Deploying Elixir Umbrella Apps for an overview.

The Makefile

Here’s the Makefile. There’s a lot going on here that I’ll try to explain.

Make Docs

.PHONY: help

AWS_PROFILE = company
AWS_REGION = us-west-2
AWS_ACCOUNT_ID = ACCOUNT_01

# APP_NAME := $(shell grep 'app:' mix.exs | sed -e 's/\[//g' -e 's/ //g' -e 's/app://' -e 's/[:,]//g')
APPS := web admin
APP_VSN := $(shell grep 'version:' mix.exs | cut -d '"' -f2)
BUILD := $(shell git rev-parse --short HEAD)

IS_PROD := $(filter prod, $(MAKECMDGOALS))
STAGE := $(if $(IS_PROD),prod,staging)

help:
	@echo "$(APP_NAME):$(APP_VSN)-$(BUILD) in $(ENV)"
	@echo " Deploying to $(STAGE)"
	@perl -nle'print $& if m{^[a-zA-Z_-]+:.*?## .*$$}' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

prod: ## Set the deploy target to prod
	@echo "Setting Prod"

admin.build: ## Make a local build
	mix do deps.get, deps.compile, compile
	cd apps/company_admin/assets && \
		yarn install && \
		yarn deploy && \
		cd .. && \
		mix phx.digest;
	cd ../../..
	mix release --profile admin:prod --verbose

web.build: ## Make a local build
	mix do deps.get, deps.compile, compile
	mix release --profile web:prod --verbose

%.docker: ecr_login ## Make a docker image
	@echo "$(basename $@) compile docker image"
	docker build --build-arg APP_NAME=$(basename $@) \
		--build-arg APP_VSN=$(APP_VSN) \
		-t $(basename $@):$(APP_VSN)-$(BUILD) \
		-t $(basename $@):latest \
		-t $(AWS_ACCOUNT_ID).dkr.ecr.$(AWS_REGION).amazonaws.com/company:$(basename $@)-$(APP_VSN)-$(BUILD) .
	docker push $(AWS_ACCOUNT_ID).dkr.ecr.$(AWS_REGION).amazonaws.com/company:$(basename $@)-$(APP_VSN)-$(BUILD)

%.dockerrun: %.docker ## Make a dockerrun file
	@echo "$(basename $@) dockerrun.aws.json"
	cp Dockerrun.aws.template.json Dockerrun.aws.json
	# Replace the <AWS_ACCOUNT_ID> with your ID
	sed -i'' "s/<ACCOUNT_ID>/$(AWS_ACCOUNT_ID)/" Dockerrun.aws.json
	# Replace the <NAME> with the your name
	sed -i'' "s/<NAME>/company/" Dockerrun.aws.json
	# Replace the <REGION> with the selected region
	sed -i'' "s/<REGION>/$(AWS_REGION)/" Dockerrun.aws.json
	# Replace the <TAG> with the your version number
	sed -i'' "s/<VERSION>/$(basename $@)-$(APP_VSN)-$(BUILD)/" Dockerrun.aws.json

%.zip: %.dockerrun ## Zip the dockerrun file
	@echo "$(basename $@) dockerrun.aws.json.zip"
	zip -r $(basename $@).zip Dockerrun.aws.json
	rm Dockerrun.aws.json

%.s3: %.zip ## Upload dockerrun zip to s3
	@echo "$(basename $@) pushing to s3"
	aws --profile=$(AWS_PROFILE) s3 cp $(basename $@).zip s3://$(basename $@)-$(STAGE)-deployments/$(basename $@)-$(STAGE)-$(BUILD).zip
	rm $(basename $@).zip

%.beanstalk: %.s3 ## Update beanstalk w/ new version
	@echo "$(basename $@) beanstalk"
	aws --profile=$(AWS_PROFILE) elasticbeanstalk create-application-version --application-name $(basename $@)-$(STAGE) --version-label $(APP_VSN):$(BUILD) --source-bundle S3Bucket=$(basename $@)-$(STAGE)-deployments,S3Key=$(basename $@)-$(STAGE)-$(BUILD).zip
	# Update the environment to use the new application version
	aws --profile=$(AWS_PROFILE) elasticbeanstalk update-environment --environment-name $(basename $@)-$(STAGE) --version-label $(APP_VSN):$(BUILD)

$(APPS): %: %.beanstalk
	@echo "deploying $@"

all: $(APPS) ## Build all apps
	@echo "Done"

%.run: ## Run the app in Docker
	docker run --env-file config/docker.env \
		--expose 4000 -p 4000:4000 \
		--rm -it $@:latest

ecr_login: ## Login to ECR
	`aws ecr get-login --profile=$(AWS_PROFILE) --no-include-email`

Running a Deploy

TIP: run make all -n to see what make is gonna do

`--> make admin -n
`aws ecr get-login --profile=company --no-include-email`
echo "admin compile docker image"
docker build --build-arg APP_NAME=admin \
                --build-arg APP_VSN=0.0.1 \
                -t admin:0.0.1-c0db6a0 \
                -t admin:latest \
                -t ACCOUNT_01.dkr.ecr.us-west-2.amazonaws.com/company:admin-0.0.1-c0db6a0 .
docker push ACCOUNT_01.dkr.ecr.us-west-2.amazonaws.com/company:admin-0.0.1-c0db6a0
echo "admin dockerrun.aws.json"
cp Dockerrun.aws.template.json Dockerrun.aws.json
# Replace the <AWS_ACCOUNT_ID> with your ID
sed -i'' "s/<ACCOUNT_ID>/ACCOUNT_01/" Dockerrun.aws.json
# Replace the <NAME> with the your name
sed -i'' "s/<NAME>/company/" Dockerrun.aws.json
# Replace the <REGION> with the selected region
sed -i'' "s/<REGION>/us-west-2/" Dockerrun.aws.json
# Replace the <TAG> with the your version number
sed -i'' "s/<VERSION>/admin-0.0.1-c0db6a0/" Dockerrun.aws.json
echo "admin dockerrun.aws.json.zip"
zip -r admin.zip Dockerrun.aws.json
rm Dockerrun.aws.json
echo "admin pushing to s3"
aws --profile=company s3 cp admin.zip s3://admin-staging-deployments/admin-staging-c0db6a0.zip
rm admin.zip
echo "admin beanstalk"
aws --profile=company elasticbeanstalk create-application-version --application-name admin-staging --version-label 0.0.1:c0db6a0 --source-bundle S3Bucket=admin-staging-deployments,S3Key=admin-staging-c0db6a0.zip
# Update the environment to use the new application version
aws --profile=company elasticbeanstalk update-environment --environment-name admin-staging --version-label 0.0.1:c0db6a0
echo "deploying admin"
rm admin.zip admin.s3 admin.docker admin.dockerrun
  1. Make and tag the docker image.
  2. Make the Dockerun.aws.json with the needed info about the version.
  3. Zip that into a APP-ENV-BUILD.zip file.
  4. Upload that to s3.
  5. Create a Beanstalk application version.
  6. Update the Beanstalk application to use the new version, causing a deploy.

The Makefile a bit more complicated because I’m using dynamic targets based on the APPS var.

Below, the %.docker is the dynamic target. I use the basename function to grab the app I’m currently working on and build the docker image for that app. Any of the targets starting with % are doing a similar thing.

%.docker: ecr_login ## Make a docker image
	@echo "$(basename $@) compile docker image"
  ...

Resources