Deploying to Google Cloud Platform (GCP)

This guide describes how to build and run your Swift Server on serverless architecture with Google Cloud Build and Google Cloud Run. We’ll use Artifact Registry to store the Docker images.

Google Cloud Platform Setup

You can read about Getting Started with GCP in more detail. In order to run Swift Server applications, we need to:

Project Requirements

Please verify that your server listens on 0.0.0.0, not 127.0.0.1 and it’s recommended to use the environment variable $PORT instead of a hard-coded value. For the workflow to pass, two files are essential, both need to be in the project root:

  1. Dockerfile
  2. cloudbuild.yaml

Dockerfile

You should test your Dockerfile with docker build . -t test and docker run -p 8080:8080 test and make sure it builds and runs locally.

The Dockerfile is the same as in the packaging guide. Replace <executable-name> with your executableTarget (ie. “Server”):

#------- build -------
FROM swift:centos as builder

# set up the workspace
RUN mkdir /workspace
WORKDIR /workspace

# copy the source to the docker image
COPY . /workspace

RUN swift build -c release --static-swift-stdlib

#------- package -------
FROM centos:8
# copy executable
COPY --from=builder /workspace/.build/release/<executable-name> /

# set the entry point (application name)
CMD ["<executable-name>"]

cloudbuild.yaml

The cloudbuild.yaml files contains a set of steps to build the server image directly in the cloud and deploy a new Cloud Run instance after the successful build. ${_VAR} are “substitution variables” that are available during build time and can be passed on into the runtime environment in the “deploy” phase. We will set the variables later when we configure the Build Trigger (Step 5).

steps:
  - name: 'gcr.io/cloud-builders/docker'
    entrypoint: 'bash'
    args:
      - '-c'
      - |
        docker pull ${_REGION}-docker.pkg.dev/$PROJECT_ID/${_REPOSITORY_NAME}/${_SERVICE_NAME}:latest || exit 0
  - name: 'gcr.io/cloud-builders/docker'
    args:
      - build
      - -t
      - ${_REGION}-docker.pkg.dev/$PROJECT_ID/${_REPOSITORY_NAME}/${_SERVICE_NAME}:$SHORT_SHA
      - -t
      - ${_REGION}-docker.pkg.dev/$PROJECT_ID/${_REPOSITORY_NAME}/${_SERVICE_NAME}:latest
      - .
      - --cache-from
      - ${_REGION}-docker.pkg.dev/$PROJECT_ID/${_REPOSITORY_NAME}/${_SERVICE_NAME}:latest
  - name: 'gcr.io/cloud-builders/docker'
    args:
      [
        'push',
        '${_REGION}-docker.pkg.dev/$PROJECT_ID/${_REPOSITORY_NAME}/${_SERVICE_NAME}:$SHORT_SHA'
      ]
  - name: 'gcr.io/cloud-builders/gcloud'
    args:
      - run
      - deploy
      - swift-service
      - --image=${_REGION}-docker.pkg.dev/$PROJECT_ID/${_REPOSITORY_NAME}/${_SERVICE_NAME}:$SHORT_SHA
      - --port=8080
      - --region=${_REGION}
      - --memory=512Mi
      - --platform=managed
      - --allow-unauthenticated
      - --min-instances=0
      - --max-instances=5
images:
  - '${_REGION}-docker.pkg.dev/$PROJECT_ID/${_REPOSITORY_NAME}/${_SERVICE_NAME}:$SHORT_SHA'
  - '${_REGION}-docker.pkg.dev/$PROJECT_ID/${_REPOSITORY_NAME}/${_SERVICE_NAME}:latest'
timeout: 1800s

The steps in detail

  1. Pull the latest image from the Artifact Registry to retrieve cached layers
  2. Build the image with $SHORT_SHA and latest tag
  3. Push the image to the Artifact Registry
  4. Deploy the image to Cloud Run

images specifies the build images to store in the registry. The default timeout is 10 minutes, so we’ll need to increase it for Swift builds. We use 8080 as the default port here, though it’s recommended to remove this line and have the server listen on $PORT.

Deployment

cloud build trigger settings and how to connect a code repository

Push all files to a remote repository. Cloud Build currently supports, GitHub, Bitbucket and GitLab. now) and head to Cloud Build Triggers and click “Create Trigger”:

  1. Add a name and description
  2. Event: “Push to a branch” is active
  3. Source: “Connect New Repository” and authorize with your code provider, and add the repository where your Swift server code is hosted. Note that you need to configure GitHub, GitLab or Bitbucket to allow GCP access first.
  4. Configuration: “Cloud Build configuration file” / Location: Repository
  5. Advanced: Substitution variables: You need to set the variables for region, repository name and service name here. You can pick a region of your choice (ie. us-central1). All custom variables must start with an underscore (_REGION). _REPOSITORY_NAME and _SERVICE_NAME are up to you. If you use environment variables for example to connect to a database or 3rd party services, you can set the values here too.
  6. “Create”

As a last step before deploying the new service, go to the Cloud Build Settings and make sure “Cloud Run” is enabled. This gives Cloud Build the necessary IAM permissions to deploy Cloud Run services.

cloud build settings

In the Trigger overview page, you should see your new “swift-service” trigger. Click on “RUN” on the right to start the trigger manually from the main branch. With a simple Hummingbird project the build takes about 7-8 minutes. Vapor takes about 25 minutes on the standard/small build machines, which are fairly slow. “Jordane” from the Vapor Discord community recommends using machineType: E2_HIGHCPU_8 in the cloudbuild.yaml to speed up deployments:

options:
  machineType: 'E2_HIGHCPU_8'

After a successful build you should see the service URL in the build logs:

successful build and deployment to cloud run

You can head over to Cloud Run and see your service running there:

cloud run overview

The trigger will deploy every new commit on main. You can also enable Pull Request triggers for feature-driven workflows. Cloud Build also allows blue/green builds, auto-scaling and much more.

You can now connect your custom domain to the new service and go live.

Cleanup