This site is deployed to Google Cloud Run using an nginx docker image with overlaid content and configuration.

Cloud Run was adopted since it is a technology I use for some professional projects and was therefore able to reuse some experience.

Additionally nginx is a technology which fits in to some of my other goals, and while alternatives such as publishing static files only may seem simpler they are also less transparent and more limiting in terms of some basic functionality such as content negotiation which can facilitate logical URLs which are more easily kept cool.

The build requires docker CLI (or compatible) to produce the images, and the gcloud CLI to autenticate with the artifact registry and trigger updates.

Those dependencies are defined here for possible overriding or extensibility such as more sophisticated feedback.


DOCKER      := docker
GCLOUD      := gcloud

 

Generated files will be placed in a conventional "build" directory to keep things tidier and cleanup more simple.


BUILD_DIR := build
$(BUILD_DIR): ; mkdir -p $(@)

 

Define the logical name of the service for use elsewhere.


SERVICE_NAME := mweb

 

The tag for the image will be generated based on the current git ref. As a side effect changes should be committed prior to building.

A pattern I often use elsewhere is to add an additional qualifier to the tag if the working directory contains any uncommitted changes which guards against inadvertently pushing an image which has local changes that deviate from what is reflected in the repository (and therefore having a misleading tag). Given the sole authorship of the content and the nature of the project itself (i.e. that it is primarily content rather than logic), I am dispensing with that additional logic for the time being.


IMAGE_TAG := $(call shell,git rev-parse --verify --short HEAD)

 

Define the image name based on the SERVICE_NAME and derived tag.


IMAGE      = $(SERVICE_NAME):$(IMAGE_TAG)

 

Build the image as necessary. This makes use of the iid file to act as a marker. The prerequisites at this point are fairly loose and likely to be refined over time.


$(BUILD_DIR)/%.iid: Dockerfile $(wildcard *) | $(BUILD_DIR)
	$(DOCKER) build --tag $(*) --iidfile $(@) .

 

Provide a phony alias for building the image.

This makes use of a pattern which will defined elsewhere which invokes a submake for the sake of a dynamic precondition without requiring secondary expansion.

image: ; $(MAKE) $(BUILD_DIR)/$(IMAGE).iid site
.PHONY: image

 

Deployment relies upon pushing the image to a repository. This was done manually rather than making use of a tool such as infrastructure-as-code, where a repository was created within Artifact Registry (which must be enabled) using the Google Cloud Console. The Google Container Registry is an older, deprecated alternative but the one I tend to stumble across first and then notice the deprecation warning.

I have also enabled immutable tags to enforce clearer traceabililty of image contents.

The region is selected as one that is geographically close to where I am located and also one of those indicated as low emission. This in turn is used to compose the host for the registry.


REGION        := us-central1
PUSH_REGISTRY := $(REGION)-docker.pkg.dev

GCP_PROJECT   := mweb-411216
GCP_SERVICE   := mweb
GCP_COMPONENT := www

 

Through the console the service account was created, and my primary user was granted the ability to impersonate that service account. The principal of the service account was then granted some needed permissions. This can be non-obvious in that the principal of the service account is managed like others rather than being directly navigable from the service account page.


PUSH_REPO_PATH := $(PUSH_REGISTRY)/$(GCP_PROJECT)/$(GCP_COMPONENT)

PUSH_SA := mweb-569@mweb-411216.iam.gserviceaccount.com

GCP_AUTH := --impersonate-service-account=$(PUSH_SA)

logged-in: ; #$(GCLOUD) auth login

$(BUILD_DIR)/pushed-%: $(BUILD_DIR)/%.iid | $(BUILD_DIR)
	$(DOCKER) tag $(*) $(PUSH_REPO_PATH)/$(*)
	$(GCLOUD) auth print-access-token $(GCP_AUTH) \
		| $(DOCKER) login -u oauth2accesstoken --password-stdin $(PUSH_REGISTRY)
	$(DOCKER) push $(PUSH_REPO_PATH)/$(*) > $(@)

push-image: ; $(MAKE) $(BUILD_DIR)/pushed-$(IMAGE)
.PHONY: push-image


 Configure custom domain
 Using beta cloud run domain mappings tucked away in the console a bit.


 Added Cloud Run Admin
 

$(BUILD_DIR)/deployed-%: $(BUILD_DIR)/pushed-%
	$(GCLOUD) $(GCP_AUTH) --project=$(GCP_PROJECT) \
	run deploy $(GCP_SERVICE) \
	--image=$(PUSH_REPO_PATH)/$(*) --region=$(REGION)
deploy: ; $(MAKE) $(BUILD_DIR)/deployed-$(IMAGE)
.PHONY: deploy