Deployment

To make deployment a easy process, I created a Makefile with different commands allowing user to deploy application with almost Zero Downtime

dpl ?= .env
include $(dpl)
export $(shell sed 's/=.*//' $(dpl))

TAG_COMMIT := $(shell git rev-list --abbrev-commit --tags --max-count=1)
TAG := $(shell git describe --abbrev=0 --tags ${TAG_COMMIT} 2>/dev/null || true)
COMMIT := $(shell git rev-parse --short HEAD)
DATE := $(shell git log -1 --format=%cd --date=format:"%Y%m%d%H%M%S")
VERSION := v$(TAG:v%=%)-$(DATE)-$(COMMIT)
BLACK        := $(shell tput -Txterm setaf 0)
RED          := $(shell tput -Txterm setaf 1)
GREEN        := $(shell tput -Txterm setaf 2)
YELLOW       := $(shell tput -Txterm setaf 3)
LIGHTPURPLE  := $(shell tput -Txterm setaf 4)
PURPLE       := $(shell tput -Txterm setaf 5)
BLUE         := $(shell tput -Txterm setaf 6)
WHITE        := $(shell tput -Txterm setaf 7)
RESET := $(shell tput -Txterm sgr0)
APPLICATION_NAME := $(shell echo $(APP_NAME) | sed -e 's/[^[:alnum:]]/-/g' | tr -s '-' | tr A-Z a-z)

ifneq ($(shell git status --porcelain),)
	VERSION := $(VERSION)-dirty
endif

FLAGS := -ldflags "-X github.com/sujit-baniya/fiber-boilerplate/app.Version=$(VERSION)"
BUILD_PATH := $(shell pwd)/build
PID := $(shell lsof -t -i:$(APP_PORT))
RELEASE_PATH := $(BUILD_PATH)/releases
SHARED_PATH := $(BUILD_PATH)/shared
CURRENT_PATH := $(BUILD_PATH)/current
CURRENT_RELEASE := "dev"
PREVIOUS_RELEASE := "dev"
ifneq ("$(wildcard $(CURRENT_PATH)/CURRENT-RELEASE)","")
    CURRENT_RELEASE := $(shell cat $(CURRENT_PATH)/CURRENT-RELEASE)
endif
ifneq ("$(wildcard $(CURRENT_PATH)/PREVIOUS-RELEASE)","")
    PREVIOUS_RELEASE := $(shell cat $(CURRENT_PATH)/PREVIOUS-RELEASE)
endif

ROLLBACK_RELEASE := $(RELEASE_PATH)/$(RELEASE_TAG)

LATEST_RELEASE := $(APPLICATION_NAME)-$(VERSION)
LATEST_RELEASE_PATH := $(RELEASE_PATH)/$(LATEST_RELEASE)

create-folder:
	$(info $(GREEN)Create Release Folder: $(LATEST_RELEASE)$(RESET))
	$(shell mkdir -p $(RELEASE_PATH)/$(LATEST_RELEASE))
	$(shell mkdir -p $(SHARED_PATH)/$(STORAGE_PATH))
	$(shell mkdir -p $(SHARED_PATH)/$(UPLOAD_PATH))

git-stash:
	$(info $(GREEN)Stashing current changes$(RESET))
	cd $(LATEST_RELEASE_PATH) && git stash

git-checkout:
	$(info $(GREEN)Checkingout Master branch$(RESET))
	cd $(LATEST_RELEASE_PATH) && git checkout master && git pull origin master

git-push:
	$(info $(GREEN)Adding all changed files and push $(RESET))
	cd $(LATEST_RELEASE_PATH) && git add . && git commit -m $(COMMIT_MESSAGE) && git push origin master

dev-push:
	$(info $(GREEN)Adding all changed files and push for dev $(RESET))
	git add . && git commit -m "$(COMMIT_MESSAGE)" && git push origin master

build-app:
	$(info $(GREEN)Building the application: $(APPLICATION_NAME)$(RESET))
	$(shell go build $(FLAGS) -o  $(RELEASE_PATH)/$(LATEST_RELEASE)/$(APPLICATION_NAME) main.go)

copy-config:
	$(info $(GREEN)Copying config, assets and .env file$(RESET))
	$(shell cp .env  $(RELEASE_PATH)/$(LATEST_RELEASE)/ && \
			cp config.yml  $(RELEASE_PATH)/$(LATEST_RELEASE)/ && \
			cp -R assets  $(RELEASE_PATH)/$(LATEST_RELEASE)/ \
	)

install-fe-dependencies:
	$(info $(GREEN)Installing Frontend dependencies$(RESET))
	$(shell yarn install >/dev/null)

compile-fe:
	$(info $(GREEN)Compiling Frontend assets$(RESET))
	$(shell yarn install >/dev/null && \
			yarn run prod >/dev/null || true \
	)

copy-assets:
	$(info $(GREEN)Copying Assets$(RESET))
		$(shell cp -R public $(RELEASE_PATH)/$(LATEST_RELEASE)/ && \
    			cp -R resources $(RELEASE_PATH)/$(LATEST_RELEASE)/ \
    	)

create-symlink:
	$(info $(GREEN)Creating Current folder symlink$(RESET))
	$(shell ln -snf $(SHARED_PATH)/$(STORAGE_PATH) $(RELEASE_PATH)/$(LATEST_RELEASE)/$(STORAGE_PATH))
	$(shell ln -snf $(SHARED_PATH)/$(UPLOAD_PATH) $(RELEASE_PATH)/$(LATEST_RELEASE)/$(UPLOAD_PATH))
	$(shell ln -snf $(RELEASE_PATH)/$(LATEST_RELEASE) $(CURRENT_PATH))
	$(shell echo $(CURRENT_RELEASE) > $(CURRENT_PATH)/PREVIOUS-RELEASE)
	$(shell echo $(LATEST_RELEASE_PATH) > $(CURRENT_PATH)/CURRENT-RELEASE)

migrate:
	$(info $(GREEN)Starting migrating$(RESET))
	cd $(CURRENT_PATH) && ./$(APPLICATION_NAME) --migrate

build: create-folder git-checkout build-app copy-config copy-assets create-symlink migrate

deploy: build restart

push: install-fe-dependencies compile-fe dev-push

start:
	$(info $(GREEN)Starting application$(RESET))
	cd $(CURRENT_PATH) && ./$(APPLICATION_NAME) </dev/null &>/dev/null &

kill:
ifneq ($(PID),)
	$(info $(RED)Stopping application on port $(APP_PORT)$(RESET))
	kill -9 $(PID)
else
	$(info $(YELLOW)Application not found on port $(APP_PORT)$(RESET))
endif

restart: kill start

rollback:
ifneq ($(PREVIOUS_RELEASE),)
	$(info Rolling Back to Previous Release: $(PREVIOUS_RELEASE))
	$(shell ln -snf $(PREVIOUS_RELEASE) $(CURRENT_PATH))
endif

rollback-to:
ifneq ($(wildcard $(ROLLBACK_RELEASE)),)
	$(info Rolling Back to Release: $(ROLLBACK_RELEASE))
	$(shell ln -snf $(ROLLBACK_RELEASE) $(CURRENT_PATH))
endif

run:
	go run $(FLAGS) main.go

install:
	go install $(FLAGS)

There are various commands involved while deployment to production. These commands are useful for development purpose as well.

Before deploying make sure you change the repository name (if you have changed it) for Flags on line:25

FLAGS := -ldflags "-X github.com/sujit-baniya/fiber-boilerplate/app.Version=$(VERSION)"

This script uses .env file so make sure .env is in-place when running the commands.

Here for deployment above command will create a build directory inside the project with three sub-directories

  • current - This is the symlink of latest release

  • releases - Contains the directories with different release versions

  • shared - Any files or folders required to be shared between the releases

Each release will be versioned under the format: <project-name>-v<Tag>-<YMDHIS>-<commit-hash>

For e.g. fiber-boilerplate-v0.0.1-20210131120000-1b1843

Command

Description

make create-folder

This command will create above mentioned three folders

for the release if not already exists

make git-checkout

This command will checkout the master branch and

pull latest changes

make git-push

This command will push all changes done on

production to repository

make dev-push

This command accept an argument COMMIT_MESSAGE and

push the changes to repository from developers platform

For e.g.

make dev-push COMMIT_MESSAGE="Test"

make build-app

This command will create build the project to binary

to latest release folder

make copy-config

This command will copy .env, config.yml and assets

from project to latest release folder

make install-fe-dependencies

This command will install front end dependencies using yarn

Make sure you've installed nodejs and yarn

make compile-fe

This command will compile the front end UI to respective

public folder

make copy-assets

This command will copy the JS, CSS, Images, Fonts, and

html files from project to current release

make create-symlink

This command will create symlink for latest release and

shared directories to current release

make migrate

This command will migrate all database changes

make build

This command is composed of:

create-folder, git-checkout, build-app, copy-config,

copy-assets, create-symlink and migrate

make kill

This command will try to kill the PID using the port

make start

This command will start the current release

make restart

This command is composed of kill and start

make deploy

This command is composed of build and restart

make rollback

This command will rollback the release to previous release

make rollback-to

This command will rollback to specific release provided

as argument.

For e.g.

make rollback-to RELEASE_TAG=project-name-20210115171923-7dc9021

Building Project

You can build the project using command make build

You can deploy the project to production using make deploy

Easy Rollback

With the command make rollback and make rollback-to you can easily rollback to previous release version or specific release version

Last updated