-
-
Save mpneuried/0594963ad38e68917ef189b4e6a269db to your computer and use it in GitHub Desktop.
# Port to run the container | |
PORT=4000 | |
# Until here you can define all the individual configurations for your app |
# You have to define the values in {} | |
APP_NAME=my-super-app | |
DOCKER_REPO={account-nr}.dkr.ecr.{region}.amazonaws.com | |
# optional aws-cli options | |
AWS_CLI_PROFILE={aws-cli-profile} | |
AWS_CLI_REGION={aws-cli-region} |
# import config. | |
# You can change the default config with `make cnf="config_special.env" build` | |
cnf ?= config.env | |
include $(cnf) | |
export $(shell sed 's/=.*//' $(cnf)) | |
# import deploy config | |
# You can change the default deploy config with `make cnf="deploy_special.env" release` | |
dpl ?= deploy.env | |
include $(dpl) | |
export $(shell sed 's/=.*//' $(dpl)) | |
# grep the version from the mix file | |
VERSION=$(shell ./version.sh) | |
# HELP | |
# This will output the help for each task | |
# thanks to https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html | |
.PHONY: help | |
help: ## This help. | |
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) | |
.DEFAULT_GOAL := help | |
# DOCKER TASKS | |
# Build the container | |
build: ## Build the container | |
docker build -t $(APP_NAME) . | |
build-nc: ## Build the container without caching | |
docker build --no-cache -t $(APP_NAME) . | |
run: ## Run container on port configured in `config.env` | |
docker run -i -t --rm --env-file=./config.env -p=$(PORT):$(PORT) --name="$(APP_NAME)" $(APP_NAME) | |
up: build run ## Run container on port configured in `config.env` (Alias to run) | |
stop: ## Stop and remove a running container | |
docker stop $(APP_NAME); docker rm $(APP_NAME) | |
release: build-nc publish ## Make a release by building and publishing the `{version}` ans `latest` tagged containers to ECR | |
# Docker publish | |
publish: repo-login publish-latest publish-version ## Publish the `{version}` ans `latest` tagged containers to ECR | |
publish-latest: tag-latest ## Publish the `latest` taged container to ECR | |
@echo 'publish latest to $(DOCKER_REPO)' | |
docker push $(DOCKER_REPO)/$(APP_NAME):latest | |
publish-version: tag-version ## Publish the `{version}` taged container to ECR | |
@echo 'publish $(VERSION) to $(DOCKER_REPO)' | |
docker push $(DOCKER_REPO)/$(APP_NAME):$(VERSION) | |
# Docker tagging | |
tag: tag-latest tag-version ## Generate container tags for the `{version}` ans `latest` tags | |
tag-latest: ## Generate container `{version}` tag | |
@echo 'create tag latest' | |
docker tag $(APP_NAME) $(DOCKER_REPO)/$(APP_NAME):latest | |
tag-version: ## Generate container `latest` tag | |
@echo 'create tag $(VERSION)' | |
docker tag $(APP_NAME) $(DOCKER_REPO)/$(APP_NAME):$(VERSION) | |
# HELPERS | |
# generate script to login to aws docker repo | |
CMD_REPOLOGIN := "eval $$\( aws ecr" | |
ifdef AWS_CLI_PROFILE | |
CMD_REPOLOGIN += " --profile $(AWS_CLI_PROFILE)" | |
endif | |
ifdef AWS_CLI_REGION | |
CMD_REPOLOGIN += " --region $(AWS_CLI_REGION)" | |
endif | |
CMD_REPOLOGIN += " get-login --no-include-email \)" | |
# login to AWS-ECR | |
repo-login: ## Auto login to AWS-ECR unsing aws-cli | |
@eval $(CMD_REPOLOGIN) | |
version: ## Output the current version | |
@echo $(VERSION) | |
### THIS IST THE VERSION WITH docker-compose | |
# import config. | |
# You can change the default config with `make cnf="config_special.env" build` | |
cnf ?= config.env | |
include $(cnf) | |
export $(shell sed 's/=.*//' $(cnf)) | |
# import deploy config | |
# You can change the default deploy config with `make cnf="deploy_special.env" release` | |
dpl ?= deploy.env | |
include $(dpl) | |
export $(shell sed 's/=.*//' $(dpl)) | |
# grep the version from the mix file | |
VERSION=$(shell ./version.sh) | |
# HELP | |
# This will output the help for each task | |
# thanks to https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html | |
.PHONY: help | |
help: ## This help. | |
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) | |
.DEFAULT_GOAL := help | |
# DOCKER TASKS | |
# Build the container | |
build: ## Build the release and develoment container. The development | |
docker-compose build --no-cache $(APP_NAME) | |
docker-compose run $(APP_NAME) grunt build | |
docker build -t $(APP_NAME) . | |
run: stop ## Run container on port configured in `config.env` | |
docker run -i -t --rm --env-file=./config.env -p=$(PORT):$(PORT) --name="$(APP_NAME)" $(APP_NAME) | |
dev: ## Run container in development mode | |
docker-compose build --no-cache $(APP_NAME) && docker-compose run $(APP_NAME) | |
# Build and run the container | |
up: ## Spin up the project | |
docker-compose up --build $(APP_NAME) | |
stop: ## Stop running containers | |
docker stop $(APP_NAME) | |
rm: stop ## Stop and remove running containers | |
docker rm $(APP_NAME) | |
clean: ## Clean the generated/compiles files | |
echo "nothing clean ..." | |
# Docker release - build, tag and push the container | |
release: build publish ## Make a release by building and publishing the `{version}` ans `latest` tagged containers to ECR | |
# Docker publish | |
publish: repo-login publish-latest publish-version ## publish the `{version}` ans `latest` tagged containers to ECR | |
publish-latest: tag-latest ## publish the `latest` taged container to ECR | |
@echo 'publish latest to $(DOCKER_REPO)' | |
docker push $(DOCKER_REPO)/$(APP_NAME):latest | |
publish-version: tag-version ## publish the `{version}` taged container to ECR | |
@echo 'publish $(VERSION) to $(DOCKER_REPO)' | |
docker push $(DOCKER_REPO)/$(APP_NAME):$(VERSION) | |
# Docker tagging | |
tag: tag-latest tag-version ## Generate container tags for the `{version}` ans `latest` tags | |
tag-latest: ## Generate container `{version}` tag | |
@echo 'create tag latest' | |
docker tag $(APP_NAME) $(DOCKER_REPO)/$(APP_NAME):latest | |
tag-version: ## Generate container `latest` tag | |
@echo 'create tag $(VERSION)' | |
docker tag $(APP_NAME) $(DOCKER_REPO)/$(APP_NAME):$(VERSION) | |
# HELPERS | |
# generate script to login to aws docker repo | |
CMD_REPOLOGIN := "aws ecr" | |
ifdef AWS_CLI_PROFILE | |
CMD_REPOLOGIN += "--profile $(AWS_CLI_PROFILE)" | |
endif | |
ifdef AWS_CLI_REGION | |
CMD_REPOLOGIN += "--region $(AWS_CLI_REGION)" | |
endif | |
CMD_REPOLOGIN += "get-login --no-include-email" | |
repo-login: ## Auto login to AWS-ECR unsing aws-cli | |
@eval $(CMD_REPOLOGIN) | |
version: ## output to version | |
@echo $(VERSION) |
# INSTALL | |
# - copy the files deploy.env, config.env, version.sh and Makefile to your repo | |
# - replace the vars in deploy.env | |
# - define the version script | |
# Build the container | |
make build | |
# Build and publish the container | |
make release | |
# Publish a container to AWS-ECR. | |
# This includes the login to the repo | |
make publish | |
# Run the container | |
make run | |
# Build an run the container | |
make up | |
# Stop the running container | |
make stop | |
# Build the container with differnt config and deploy file | |
make cnf=another_config.env dpl=another_deploy.env build |
# Example version script. | |
# Please choose one version or create your own | |
# Node.js: grep the version from a package.json file with jq | |
jq -rM '.version' package.json | |
# Elixir: grep the version from a mix file | |
cat mix.exs | grep version | grep '\([0-9]\+\.\?\)\{3\}' -o |
This is awesome, thank you!
Thanks for this, this is great.
One thing though, your help text for tag-latest
and tag-version
is exchanged; in both makefiles (docker and docker-compose).
Hi Folks, what is the benefit it serves to maintain additional makefile when a separate service can manage their own Dockerfile and can be spin up by anyone , I would appreciate some reference docs or insight on the benefits to go down the road to maintain 2 files for a single service , in the kubernetes model it would just be a run time that would matter so how does this approach benefit ?
@shapeofarchitect there are a bunch of articles on the benefits of makefiles or some build system like Bazel, so I won't go into a lot of detail; mainly for the sake of time, since I'm only passing through the interwebs and happened to run into this gist.
so I think what you're saying is that services like Dockerhub have auto-build/web-hook methods of providing artifacts and publishing these artifacts (am I correct in assuming this is what you're asking)? if so, consider this:
not all docker registries have auto-build/webhook methods that can take advantage of the --build-args
feature in docker. for example, Quay, which has been our preference, continually says that they're releasing the feature, but I think that acquisition after acquisition has pushed down the priorities over the last couple of years, unfortunately.
so let's say that your docker registry of choice does not take advantage of the --build-args
flags, as is our case. also, let's say that you have some dockerfile that looks like this (a very simple golang-based project):
FROM golang:1.11
ARG GOOS=linux
ARG GOARCH=amd64
ENV GOOS=$GOOS
ENV GOARCH=$GOARCH
WORKDIR /go/src/github.com/v1k0d3n/myapp
COPY . .
RUN go get -d -v ./...
RUN CGO_ENABLED=0 go build -ldflags '-w -s' -a -installsuffix cgo -o /myapp
FROM scratch AS build
COPY --from=0 /myapp /myapp
VOLUME /data
ENTRYPOINT [ "/myapp" ]
CMD [ "--help" ]
the first thing you'll probably recognize is that we're leveraging docker's multi-stage build process. you likely already know this, but for others landing on this gist who may not, it allows us to build a golang application from a private repo, use our ssh keys to pull down other private go-lang based dependencies we may require (our example application does) and then throw away the build environment/keys, leaving only the application binary.
there's really no good way to do this well with a build-system like Dockerhub (i could be wrong about this since I haven't checked in a while). golaang makes things a big tricky, since we need to build the version information into the application using -ldflags
option at buildtime. if I were to do this all via bash, say build, package, and push that application to Quay, i could possibly do something like:
export VERSION="v0.2.4"
export COMMIT="$(git rev-parse --short HEAD)"
export REGISTRY="quay.io"
export NAMESPACE="v1k0d3n"
if [ $(git tag -l "${VERSION}") ]; then
echo "Git tag ${VERSION} already exists. Continuing..."
else
echo "Git tag \"${VERSION}\" not found! Creating tag based on commit \"${COMMIT}\"."
git tag ${VERSION}
git push origin ${VERSION}
fi
docker build --build-arg COMMIT="${COMMIT}" --build-arg VERSION="${VERSION}" -t "${REGISTRY}/${NAMESPACE}/myapp:${VERSION}" .
docker build --build-arg COMMIT="${COMMIT}" --build-arg VERSION="${VERSION}" -t "${REGISTRY}/${NAMESPACE}/myapp:${VERSION}-${COMMIT}" .
docker push "${REGISTRY}/${NAMESPACE}/myapp:${VERSION}"
docker push "${REGISTRY}/${NAMESPACE}/myapp:${VERSION}-${COMMIT}"
but makefiles have an additional logic that is a bit more build-oriented than bash in the fact that it can track which targets require rebuilding, etc. overall, my point is that with simple dockerfiles; sure you have an extremely valid point. once you start getting into more complex application builds using docker, the solution isn't as simple or straightforward. the person can create the dockerfile, if they have access to the repo, sure, but what if we want to (in this case) create a docker image and also build an artifact for the system that user may be on? in our case example above this application needs to run on MacOS, and Linux. I want a single build file to handle all of this logic based on a command structure.
make build
make test
make push
And a Makefile is the best way that users and CI platforms can accomplish this.
Do you get this error for make run?
run:
@echo "Run docker image
docker run -d -i -t --rm --env-file=./config.env -p=$(PORT):$(PORT) --name="$(APP_NAME)" $(APP_NAME)
vagrant@linux:$ make run
make: Circular 8888 <- 8888 dependency dropped.
-p argument PORT:PORT is causing make to treat that line a circular dependency
Escaping colon like this worked:
Escaping colon like this worked:
$(PORT):$ (PORT)
You mentioned on https://gist.github.com/mpneuried/0594963ad38e68917ef189b4e6a269db#gistcomment-2896932 that you used -p
Isn't it a best practice to specify all
as your default target instead of help https://www.gnu.org/prep/standards/html_node/Standard-Targets.html#Standard-Targets
@kamalhussain You're right, the
--no-cache
inmake release
is in fact useless ;-)