There are a couple of components to the setup. Let’s cover them one by one.

GCP Terraform

It uses terraform to deploy builders for gitlab.

Create a Terraform API Token

Using a remote state is always a good idea, in case multiple people need to apply a terraform change. I ended up using the free terraform cloud provider to hold the state. After you create an account and a project you can use the Using Terraform Cloud Remote State Management to get the values to be used with terraform. Update the backend.tf file with appropriate values.

Setup your Terraform Env

Create and download service account private key:

export PROJECT_ID=$(gcloud config list --format 'value(core.project)')
gcloud iam service-accounts keys create \
      --iam-account terraform@${PROJECT_ID}.iam.gserviceaccount.com credentials.json

The credentials can be provided in multiple ways and each option is covered in Google Provider Configuration Reference, but by default creating a credentials.json in the terraform-gcp directory will work. Next create a tfvars file from the template:

cp terraform.tfvars.template terraform.auto.tfvars

And update the missing parameters, like gcp_project and so forth.

Create the Runner

Run the following to start the install:

terraform init
terraform plan
tearrform apply -auto-approve

If you go to Settings -> CI/CD -> Runners you will see the runner up and registered.

Deleting the Runner

By default with Terraform Cloud you need to set an extra environment variable at the project level to allow deletion of terraform resources ( this is covered in Clean Up Cloud Resources). After that’s set then you can run the following to clean up:

terraform destroy -auto-approve

Creating CI/CD Pipelines in Gitlab

After the installation is finished you can use cloud shell to ssh into the machine for any sort of troubleshooting:

$ gcloud compute ssh gitlab-ci-runner --zone us-east4-c

Then confirm the service is running:

   Memory: 12.6M
   CGroup: /system.slice/gitlab-runner.service
           └─2602 /usr/lib/gitlab-runner/gitlab-runner run --working-directory /home/gitlab-runner --config /etc/gitlab-runner/config.toml --service gitlab-runner --syslog --user gitlab-runner

Mar 26 21:26:50 gitlab-ci-runner gitlab-runner[2602]: Running in system-mode.
Mar 26 21:26:50 gitlab-ci-runner gitlab-runner[2602]: Running in system-mode.
Mar 26 21:26:50 gitlab-ci-runner gitlab-runner[2602]:
Mar 26 21:26:50 gitlab-ci-runner gitlab-runner[2602]:
Mar 26 21:26:50 gitlab-ci-runner gitlab-runner[2602]: Configuration loaded                                builds=0
Mar 26 21:26:50 gitlab-ci-runner gitlab-runner[2602]: Configuration loaded                                builds=0

Creating a firebase container image

Now let’s create a simple job to run a firebase command from a builder. This is described in Deploying to Firebase. In the cloud shell, create a Dockerfile with the following contents:

# use latest Node LTS
FROM node:alpine
# install Firebase CLI
RUN npm install -g firebase-tools

Then build it and push it:

$ export PROJECT_ID=$(gcloud config list --format 'value(core.project)')
$ gcloud container images list
Listed 0 items.
Only listing images in gcr.io/GCP_PROJECT. Use --repository to list images in other repositories.
$ docker build -t gcr.io/${PROJECT_ID}/firebase-cli .
$ docker push gcr.io/${PROJECT_ID}/firebase-cli

Get a firebase token key. From cloud shell run the following:

$ firebase login:ci --no-localhost

Save the output and create a variable in gitlab as described in How to leverage GitLab CI/CD for Google Firebase.

Creating a simple .gitlab-ci.yaml

Lastly create a simple .gitlab-ci.yaml file which just lists available projects:

image: gcr.io/${PROJECT_ID}/firebase-cli

deploy-functions:
  stage: deploy
  tags:
    - docker
    - gce
    - specific
  script:
    - firebase projects:list --token $FIREBASE_TOKEN
  only:
    refs:
      - master

If you just commit that to the repo, it will automatically start the build. As a quick test I installed python-gitlab and created an access token for gitlab so I could query status of gitlab pipelines. First get the gitlab project ID and Job IDs. To get the ProjectID, you can run the following:

> gitlab project list --owned TRUE
id: GITLAB_PROJECT_ID
path: repo

And to get the JOB_ID, you can run the following:

> gitlab project-job list --project-id ${GITLAB_PROJECT_ID} | head -1
id: GITLAB_JOB_ID

When I checked the status of my job I saw the following:

> gitlab project-job trace --project-id ${GITLAB_PROJECT_ID} --id ${GITLAB_JOB_ID}
Running with gitlab-runner 12.9.0 (4c96e5ad)
  on GCP_PROJECT_ID ToQevJz8
Preparing the "docker" executor
Using Docker executor with image gcr.io/GCP_PROJECT_ID/firebase-cli ...
Authenticating with credentials from /root/.docker/config.json
Pulling docker image gcr.io/GCP_PROJECT_ID/firebase-cli ...
Using docker image sha256:a4b5bdd020624c74aba15a for gcr.io/GCP_PROJECT_ID/firebase-cli ...
Preparing environment
Running on runner-ToQevJz8-project-GITLAB_PROJECT_ID-concurrent-0 via gitlab-ci-runner...
Getting source from Git repository
Fetching changes with git depth set to 50...
Initialized empty Git repository in /builds/NAME/utils/.git/
Created fresh repository.
From https://gitlab.com/NAME/REPO
 * [new ref]         refs/pipelines/GITLAB_JOB_ID -> refs/pipelines/GITLAB_JOB_ID
 * [new branch]      master                   -> origin/master
Checking out 06082c0a as master...

Skipping Git submodules setup
Restoring cache
Downloading artifacts
Running before_script and script
Authenticating with credentials from /root/.docker/config.json
$ firebase projects:list --token $FIREBASE_TOKEN
- Preparing the list of your Firebase projects
✔ Preparing the list of your Firebase projects
┌──────────────────────┬───────────────────┬──────────────────────┐
│ Project Display Name │ Project ID        │ Resource Location ID │
├──────────────────────┼───────────────────┼──────────────────────┤
│ FIREBASE_PROJECT     │ GCP_PROJECT_ID    │ us-central           │
└──────────────────────┴───────────────────┴──────────────────────┘

1 project(s) total.
Running after_script
Saving cache
Uploading artifacts for successful job
Job succeeded

Pretty cool to see all the components come together.