Building and Pushing Your Dockerized Node App in Google Container Registry

A guide to building a Docker Image of your Node App and Making it Available in Google Cloud Container Registry
October 27th, 2019  by Blaine Garrett

In my previous post, I explained how to locally Dockerize a Node application. When we ended, we were able to build a Docker image of a Next.js app for production and run it locally. We're going to pick up where we left off and build and push our Docker images in Google Cloud Build. If you are not familiar with Google Cloud Build, read up on it. It's a powerful CI/CD tool from Google that allows us to run builds by defining steps using yaml syntax. Under the hood each step is a Docker container of its own. You can even make your own steps, which I'll do in a future post.

Goals

  • Set up Google Cloud Build for our project
  • Write a simple Hello World build script 
  • Build a Docker image of our production Node.js application
  • Push our Docker Image to Google Container Registry

 

Before We Begin

  • You have a Node.js app with a production Dockerfile (living in ./ci/build.production.Dockerfile) and have tested it by building locally. I will be using my node-next-gae-demo . If you are following along, I am using the v.0.3.1 release as a starting point.
  • You have a Google Cloud account and a project with project_id handy. I will be using my blaine-garrett project. In all the examples below, replace blaine-garrett with your project id.

 

Step 1: Set up Cloud Build For Your Google Cloud Project

If you have not used Google Cloud yet with your project, be sure visit the Google Cloud Build page in the Developer console. If you have been using Google App Engine, you may have a build history already.

Next write a simple build script to kick off a build. Put the following into ./ci/example.cloudbuild.yaml 

####################################
# Cloud Build Example File         #
####################################
steps:
  - name: 'gcr.io/cloud-builders/gsutil'
    entrypoint: 'bash'
    args:
      - '-c'
      - |
        echo "Hello World - this is step 1"

  - name: 'gcr.io/cloud-builders/gsutil'
    entrypoint: 'bash'
    args:
      - '-c'
      - |
        echo "Hello World - this is step 2"

Next switch into your ./ci directory and run the build. Doing so will prevent having to upload your project to cloud build.

gcloud builds submit --config=./example.cloudbuild.yaml --project=blaine-garrett

This should generate something like:

Creating temporary tarball archive of 4 file(s) totalling 1.1 KiB before compression.
Uploading tarball of [.] to [gs://blaine-garrett_cloudbuild/source/1572186587.89-fd136181801341c08dcec59579f2539b.tgz]
Created [https://cloudbuild.googleapis.com/v1/projects/blaine-garrett/builds/6d63f77e-361e-4ddf-82ab-f97f53f98ae3].
Logs are available at [https://console.cloud.google.com/gcr/builds/6d63f77e-361e-4ddf-82ab-f97f53f98ae3?project=1069402597832].
---------------------------------------------------------------------------------- REMOTE BUILD OUTPUT ----------------------------------------------------------------------------------
starting build "6d63f77e-361e-4ddf-82ab-f97f53f98ae3"

FETCHSOURCE
Fetching storage object: gs://blaine-garrett_cloudbuild/source/1572186587.89-fd136181801341c08dcec59579f2539b.tgz#1572186589870838
Copying gs://blaine-garrett_cloudbuild/source/1572186587.89-fd136181801341c08dcec59579f2539b.tgz#1572186589870838...
/ [1 files][  706.0 B/  706.0 B]                                                
Operation completed over 1 objects/706.0 B.                                      
BUILD
Starting Step #0
Step #0: Already have image (with digest): gcr.io/cloud-builders/gsutil
Step #0: Hello World - this is step 1
Finished Step #0
Starting Step #1
Step #1: Already have image (with digest): gcr.io/cloud-builders/gsutil
Step #1: Hello World - this is step 2
Finished Step #1
PUSH
DONE
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

ID                                    CREATE_TIME                DURATION  SOURCE                                                                                    IMAGES  STATUS
6d63f77e-361e-4ddf-82ab-f97f53f98ae3  2019-10-27T14:29:50+00:00  7S        gs://blaine-garrett_cloudbuild/source/1572186587.89-fd136181801341c08dcec59579f2539b.tgz  -       SUCCESS

 

 

Next, go to the Google Cloud Build history for your project and you should see something like the following:

 

Step 2: Build Image of our Node.js app and Push Image to Google Container Registry

This is a suprisingly quick step.

Firstly, check out what (if any) containers are already in your Google Container Registry. If you have been using Google App Engine, you might have some containers preasent. 

 

Next, similar to our .dockerignore file, we want to add a Cloud Ignore file so things like our .git and node_modules directories do not get uploaded to Cloud Builder.

Add the following to ./ci/build.gcloudignore

# This file specifies files that are *not* uploaded to Google Cloud Platform
# using gcloud. It follows the same syntax as .gitignore, with the addition of
# "#!include" directives (which insert the entries of the given .gitignore-style
# file at that point).
#
# For more information, run:
#   $ gcloud topic gcloudignore
#
.gcloudignore
# If you would like to upload your .git directory, .gitignore file or files
# from your .gitignore file, remove the corresponding line
# below:
.git
.gitignore

# Node.js dependencies:
node_modules/
*.hot-update.js*

# Other config and Files
README.md
.eslintignore
.eslintrc

 

Finally, add the following to ./ci/build.production.cloudbuild.yaml:

####################################
# Build to Generate Docker Image   #
####################################
steps:
  - name: 'gcr.io/cloud-builders/docker'
    args:
      [
        'build',
        '-t',
        'gcr.io/$PROJECT_ID/gae-node-next-demo:prod',
        '-f',
        './ci/build.production.Dockerfile',
        '.',
      ]

images: ['gcr.io/$PROJECT_ID/gae-node-next-demo']

 

Then run in the project root directory, run:

gcloud builds submit --ignore-file=./ci/build.gcloudignore --config=./ci/build.production.cloudbuild.yaml --project=blaine-garrett

Note: $PROJECT_ID is a Environent Variable Cloud Buiild passes to your build. It is set to your GCloud project id (eg. blaine-garrett). Read more on variable substitution in builds.

 

Once the above command, reload the Google Container Registry page and see your image. You should have a folder with the name of your image (eg. gae-node-next-demo) and it it should be a Docker container with the tag "prod" such as below:

If you see the above, you are done. Celebrate.

 

Next Steps

We can now use Google Cloud Build to build a Docker image of our production Node.js application and push it to our Google Container Registry. The next steps are:

  • Deploy a container from the container registry to any number of Google Cloud hosting services that support containers. 
  • Modify our cloud build script to run jest tests prior to building the Docker image.
  • Trigger these builds in some fashion from Github

 

Related Links