· Péhápkaři Prague meetup #30 · Prague · given in Czech

Auto-deploying to Google App Engine with GitLab CI

Autodeploy na Google App Engine s Gitlab CI. Every push to the repository ends with the app live on Google App Engine, and nobody runs a deploy command by hand. It takes five steps, and most of them are clicking through the Google Cloud console once.

Update, 2026Two things are different today. The php55 runtime used below is a first-generation App Engine runtime: it reached end of support in January 2024 and was deprecated in January 2026, so use a current PHP runtime. And instead of storing a service account key in a CI variable, GitLab can now authenticate to Google Cloud with Workload Identity Federation, which needs no long-lived key at all.

What you need, and what App Engine is

Three things: a Google Cloud account, a GitLab account, and an app to deploy.

Google App Engine (GAE) is Google’s “fully managed serverless app platform”. You give it your code and a small config file, and it runs the app. You pay only for what you use, and it scales instantly. No servers to install or patch.

The whole process

  1. Create a project on Google Cloud.
  2. Create a Google Cloud service account.
  3. Hand its credentials to GitLab CI.
  4. Enable the App Engine Admin API.
  5. Configure the deploy in .gitlab-ci.yml.

The app itself is as small as it gets:

<?php

echo 'Hello everyone at LMC!';
echo '<br>';
echo 'Grab a beer and enjoy the moment!';

exit(0);

Step 1: a project, and why not the CLI

First a new project in the Google Cloud console. Mine was called lmc-gae-autodeploy-demo. Remember that the project ID can’t be changed later.

The “proper” way to set up App Engine is the command line: gcloud init. On my laptop that ended in a long Python traceback. The Cloud SDK was built on Python 2.7, and the local install failed to load its sqlite3 extension. gcloud app deploy failed with the same error. Nope.

So I went through the web console instead. The App Engine section has an “Your first app with PHP” wizard: pick a region (I used us-central), then follow a quickstart that runs in the browser. It’s actually good.

To make the app ready for App Engine you add one file, app.yaml, to the root of the repository:

runtime: php55
api_version: 1
threadsafe: true

handlers:
  - url: /.*
    script: public/index.php

It says which runtime to use and sends every URL to public/index.php. That’s the whole config.

The broken local SDK turned out to be a good argument for the rest of the talk: if deploying depends on a working setup on someone’s laptop, it will break. Let the CI do it.

Step 2: a service account

A service account is a Google Cloud identity for a machine rather than a person. GitLab CI will log in as this account.

Step 3: credentials into GitLab CI

In the GitLab project, under Settings → CI/CD → Variables, I created two variables:

Variables are passed to the job by the runner, so the key never sits in the repository. GitLab can also restrict them to protected branches.

Step 4: enable the App Engine Admin API

In APIs & Services → Library, search for “google app engine” and enable the App Engine Admin API. That’s the API the deploy goes through, and the service account can’t deploy until it’s on.

Step 5: .gitlab-ci.yml

The first version installs the Google Cloud SDK into a python:2.7 image, writes the key from the variable into a temporary file, logs in with it and deploys:

image: python:2.7

before_script:
  - echo "deb http://packages.cloud.google.com/apt cloud-sdk-jessie main" | tee /etc/apt/sources.list.d/google-cloud-sdk.list
  - curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
  - apt-get update && apt-get install google-cloud-sdk

after_script:
  - rm /tmp/$CI_PIPELINE_ID.json

deploy:
  stage: deploy
  script:
    - echo $GOOGLECLOUDACCOUNT > /tmp/$CI_PIPELINE_ID.json
    - gcloud auth activate-service-account --key-file /tmp/$CI_PIPELINE_ID.json
    - gcloud --quiet --project $GOOGLECLOUDPROJECT app deploy

The key file is named after the pipeline ID, and after_script deletes it when the job ends. --quiet stops gcloud from asking questions nobody will answer in CI.

Installing the SDK on every run is slow, so the second version uses a prepared Docker image with dpl, a deploy tool from Travis CI that knows how to deploy to App Engine:

image: registry.gitlab.com/entrydo/docker/dpl:latest

stages:
  - deploy

production:
  stage: deploy
  script:
    - echo $GOOGLECLOUDACCOUNT > /tmp/$CI_PIPELINE_ID.json
    - dpl --provider=gae --keyfile=/tmp/$CI_PIPELINE_ID.json
  after_script:
    - rm /tmp/$CI_PIPELINE_ID.json

The result

GitLab CI job log: uploading 4 files to Google Cloud Storage, updating service default, setting the traffic split, then 'Deployed service [default] to https://lmc-gae-autodeploy-demo.appspot.com', the after script removing the key file, and 'Job succeeded'. Below it a green production job in the pipeline.
The deploy job: four files uploaded, service updated, app live on appspot.com.

Push, and the pipeline uploads the files, updates the service and prints the URL where the app now runs. Then came the live demo, with the usual “what could go wrong?”. The recording is linked below, in two parts.

#movingtogitlab

I closed with the GitLab chart of GitHub imports from early June 2018, when Microsoft announced it was buying GitHub. Imports went from a few hundred a day to over 100,000. The demo project is at gitlab.com/Lexinek/lmc-gae-autodeploy.

All 29 slides
  1. Slide 1: Title: Jan Mikeš, 13.9.2018, GitLab CI Google App Engine autodeploy, with speaker photo
  2. Slide 2: Photo of a sheet cake baking in an oven
  3. Slide 3: What is Google App Engine? Fully managed serverless app platform, pay only what you use, instant scalability
  4. Slide 4: Deploy steps: create GC project, service account, pass credentials to GitLab CI, enable GAE Admin API, configure .gitlab-ci.yml
  5. Slide 5: App: a PHP hello world printing “Hello everyone at LMC! Grab a beer and enjoy the moment!”
  6. Slide 6: Creating the lmc-gae-autodeploy-demo project in the Google Cloud console
  7. Slide 7: App Engine via CLI: gcloud init
  8. Slide 8: App Engine via CLI: gcloud failing with a Python sqlite3 import error
  9. Slide 9: SpongeBob rainbow meme captioned “Nope” after the error
  10. Slide 10: App Engine console: choosing a region for your first PHP app
  11. Slide 11: Awesome quickstart: the App Engine quickstart tutorial in Cloud Shell
  12. Slide 12: Get our app ready for GAE: app.yaml with runtime php55 routing all URLs to public/index.php
  13. Slide 13: Deploy our app: gcloud app deploy
  14. Slide 14: Deploy our app: gcloud app deploy failing with the same Python sqlite3 error
  15. Slide 15: SpongeBob rainbow meme captioned “Nope” (continued)
  16. Slide 16: Creating a service account: IAM & admin → Service accounts in the Cloud console menu
  17. Slide 17: Creating a service account: Create service account button in the project
  18. Slide 18: Creating a service account: naming it gitlab-ci and picking the Editor role
  19. Slide 19: Creating a service account: furnishing a new JSON private key
  20. Slide 20: Enabling the GAE API: APIs & Services → Library
  21. Slide 21: Enabling the GAE API: searching for and selecting App Engine Admin API
  22. Slide 22: GitLab CI variables: service-account JSON in a GitLab CI variable, key redacted, plus project ID
  23. Slide 23: .gitlab-ci.yml: install Cloud SDK, activate service account from variable, gcloud app deploy
  24. Slide 24: .gitlab-ci.yml: production deploy job using dpl --provider=gae with the key file
  25. Slide 25: GitLab CI pipeline: job log deploying to lmc-gae-autodeploy-demo.appspot.com, job succeeded
  26. Slide 26: Disaster girl meme: “Time for a live demo, what could go wrong?”
  27. Slide 27: #movingtogitlab: GitLab chart of GitHub imports spiking sharply
  28. Slide 28: Link to the demo repository: gitlab.com/Lexinek/lmc-gae-autodeploy
  29. Slide 29: Homer Simpson asking “Questions?” and Thank you! janmikes.cz, j.mikes@me.com, entry.do

Related talks