close
address. Gaston Geenslaan 11 B4, 3001 Leuven
s.
All Insights

Flutter Web on Google App Engine using Cloud Build

In this blog, we will explain the deployment process to Google App Engine for a Flutter Web application, orchestrated by Google Cloud Build.

Written by
Matteus Deloge

In this blog, we will explain the deployment process to Google App Engine for a Flutter Web application, orchestrated by Google Cloud Build.


Why are we writing this post? 🧐

We love Flutter at Craftworkz. Period.

It’s beautifully powerful, developer-friendly, and has recently shed its mobile-oriented shell by going full cross-platform with the launch of Flutter 2 (and Flutter 2.2 has just been announced at Google I/O).

We have already built a lot of web apps using a wide range of developer frameworks, most notably React and Angular. The question now asks itself: “Why not fully switch to Flutter as we love it so much when building mobile apps and Flutter Web is now in GA?”

To be ready for production usage by our clients, an important requirement must be met by Flutter Web. A fully automated CI/CD pipeline needs to be able to build and deploy our application upon each commit on the main branch of our repository. Essentially, manual deploys are out of the question.

So, what will we discuss in this post?

  • Setting up an App Engine service, hosting your Flutter Web application
  • How to enable Cloud Build to automatically keep your deployment in sync with your codebase
  • General thoughts on the whole process

The source code and demo setup are also available as a public Github repository here. Feel free to suggest updates there in the future or below this post. Let’s dive in!

Setting up your Google Cloud project 🤘🏼

Okay, let’s start at the beginning. First, you’ll need a Google Cloud project to be able to deploy your application. Duh.

If you have trouble with any of the following steps, you can find answers to most of your questions on Google’s Documentation pages and configure your project using the GCP Console.

  1. Create a Google Cloud project with a descriptive name. We created a demo project with the name craftworkz-blog-flutter-demo
  2. Enable billing for your project.
    Hosting the Flutter Web application will most likely be free as Google’s free tier is quite large, but you’ll need the billing account to be able to use the components used in this post.

    And if you’re a new GCP user, you get €300 in free credits.
  3. Setup the Google Cloud SDK for your machine.
gcloud init
gcloud config set project MY_PROJECT
gcloud config set region europe-west1

If this setup was successful, it’s a good idea to enable some APIs for future usage.

gcloud services enable cloudbuild.googleapis.com
gcloud services enable appengine.googleapis.com

Testing your Flutter Web application locally 👀

We assume you have created a Flutter Web application locally that works when running it locally using flutter run -d chrome

We noticed that sometimes, the local Flutter runner has some extra tricks up its sleeve to keep your application running smoothly while the production build will not work as expected. For example, when loading an Image asset using the Image.network(...) function, the Flutter runtime locally doesn’t throw an error, but when hosting the built version, it does throw a 404.

There is a simple solution to test if your application has this issue. Simply run and test the built version locally first:

flutter build web
cd build/web
python3 -m http.server 8000

If everything runs as expected, we’re good to go! If not, you might want to check your web console output (F12 for dev tools) or go back to the Flutter runner output to see if you have any remaining rendering issues.

Be sure to test a few different desktop and mobile screen sizes and orientations before deploying your current version. We have experienced some issues during development due to our code not being responsive enough to cope with small mobile screens (there are still lots of people using iPhone SE-type screens out there, and these are smaller than you think).

Simple App Engine deployment setup 🦾

First, let’s make sure we can deploy to App Engine and have a running version of our Flutter Web application on App Engine deployed from our local machine before we set up the full Cloud Build pipeline.

First, enable App Engine and create an application in a region of your choice, if you haven’t done so already using the web console:

gcloud app create --region=europe-west

Then add an app.yaml file to your project that describes your App Engine application and redirects all traffic to HTTPS and your build/web folder:

env: standard
runtime: nodejs14

handlers:
- url: /
static_files: build/web/index.html
upload: build/web/index.html
secure: always
redirect_http_response_code: 301

- url: /(.*)
static_files: build/web/\1
upload: build/web/(.*)
secure: always
redirect_http_response_code: 301

This works fine but is not as clean as we might want, because we’re uploading our source files to App Engine, while we only need the build/web static contents.

So, if you do want to do it cleanly, use the following app.yaml configuration and add the file to the build/web dir before you push to App Engine:

env: standard
runtime: nodejs14

handlers:
- url: /
static_files: index.html
upload: index.html
secure: always
redirect_http_response_code: 301

- url: /(.*)
static_files: \1
upload: (.*)
secure: always
redirect_http_response_code: 301

Remember: don’t add it to the build/web directory in your source code, as this directory should not be committed to Git, and files are overwritten at build time. Add the app.yaml to your project root and copy it to the build/web directory before deploying to App Engine.

Let’s push our app to App Engine! (remember to build your code first by running flutter build web before attempting a deploy).

Using the clean method, first push the app.yaml file to the build dir and deploy only the built assets:

cp app.yaml build/web
cd build/web
gcloud app deploy

... or when using the working, but less clean, first app.yaml config approach, you can just deploy from the project root:

gcloud app deploy

If everything went correctly, your application should now be up, running, and available at the URL provided by the console output. For our demo project, you can check it out here: https://craftworkz-blog-flutter-demo.ew.r.appspot.com, if you want to.

Let’s automate this deployment using Cloud Build ☁️

Cloud Build is another one of Google’s tooling that we just love. Any pipeline can be configured using the console or by adding a cloudbuild.yaml file in your repo. Make sure to give Cloud Build enough IAM rights to access all needed services by going to the GCP console Cloud Build settings and enabling the App Engine Admin and Service Account User roles.

Settings Flutter - Cloud build
Enable the App Engine Admin and Service Account User roles.

First make sure your source code is available on Cloud Source Repositories, the git repository tool provided by GCP. You can do this in 2 ways:

  1. Use Source Repositories as your main and only git repository by creating it there;
  2. Link Source Repositories to your repo, hosted by a third party like Bitbucket or Gitlab. You can also reference our public Github repository containing all the needed code.
Cloud source repositories Flutter
Connecting your GitHub repository to Google Cloud Source Repositories is really easy.

Secondly, we need a Cloud Build runner that can build our Flutter code for the web. At the time of writing this, there is no standard Cloud builder image available for Flutter (see list here). But here’s where Cloud build shines because there is a whole list of community-maintained builders available that you can build yourself, to use in your pipelines. And yes, Flutter is supported!

To build your Flutter builder image, follow these steps (code below list):

  1. Clone the community builders repository to your machine
  2. Navigate to the Flutter folder
  3. Build all versions of the Flutter builder using Cloud Build. These will be available within your project’s Container Registry
git clone https://github.com/GoogleCloudPlatform/cloud-builders-community
cd cloud-builders-community/flutter
gcloud builds submit --config cloudbuild.yaml .

If you are not planning to do an Android build with this image in the future, or when you are running into Android license errors during this build, you can change the provided Dockerfile in the cloud-builders-community/flutter folder:

FROM debian:stretch

# Install Dependencies.
RUN apt update -y
RUN apt install -y \
git \
wget \
curl \
unzip \
lib32stdc++6 \
libglu1-mesa \
default-jdk-headless

# Install Flutter.
ENV FLUTTER_ROOT="/opt/flutter"
RUN git clone https://github.com/flutter/flutter "${FLUTTER_ROOT}"
ENV PATH="${FLUTTER_ROOT}/bin:${PATH}"

# Disable analytics and crash reporting on the builder.
RUN flutter config --no-analytics

# Perform an artifact precache so that no extra assets need to be downloaded on demand.
RUN flutter precache

# Perform a doctor run.
RUN flutter doctor -v

# Switch to the correct channel
ARG channel=stable
RUN flutter channel $channel

# Perform a flutter upgrade
RUN flutter upgrade

ENTRYPOINT [ "flutter" ]

This configuration is available in our Github repository as well, so you can simply run:

# from repository root
cd cloud-builder-flutter
gcloud builds submit --config cloudbuild.yaml .

After this build has been completed, your GCP projects Container Registry will contain a Flutter builder image at gcr.io/$PROJECT_ID/flutter:stable, usable in the Cloud Build pipeline. You can confirm this by going to your projects Container Registry page at https://console.cloud.google.com/gcr/images/$YOUR_PROJECT_ID.
Isn’t that just amazing?

Now, let’s add the cloudbuild.yaml which will define our build steps for Cloud Build (we assume you have the second app.yaml file present in the root directory of your project):

steps:
- name: 'gcr.io/$PROJECT_ID/flutter:stable'
args: ['build', 'web']
- name: gcr.io/google.com/cloudsdktool/cloud-sdk
args:
- '-c'
- cp app.yaml build/web && cd build/web && gcloud config set app/cloud_build_timeout 1600 && gcloud app deploy
entrypoint: bash
timeout: 1600s

When all these files have been committed and pushed to Source Repositories or your connected repository, you can create the build trigger in the Cloud Build dashboard:

Cloud build dashboard
Sample Cloud Build trigger configuration

Et voilà, your setup should now be complete! Upon each push to the main branch, your app should be automatically rebuilt and deployed. 💪


Is something not working as expected, or do you just want to check out a demo repository containing the configuration described in this post? Go check out the Github repository containing everything discussed in this post!

Want to see a Flutter web page in action using this pipeline? Check out our event page for Raccoons Reimagines or the sample demo page built for this blogpost.


Are you interested in building your Flutter-based web application and looking for technical and creative support? Contact us at info@craftworkz.be or fill out the contact form on our website.

Written by
Matteus Deloge

Subscribe to our newsletter

More blog articles that can inspire you

21/06/2021

“Enhance!” - My internship at Brainjar on image enhancing

A story about my time at Brainjar as an intern, where I work on CSI-style image enhance AI including super-resolution, c...

what we do

Socials

address. Gaston Geenslaan 11 B4, 3001 Leuven