In this blog, we will explain the deployment process to Google App Engine for a Flutter Web application, orchestrated by Google Cloud Build.
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?
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!
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.
craftworkz-blog-flutter-demo
gcloud initgcloud config set project MY_PROJECTgcloud 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.comgcloud services enable appengine.googleapis.com
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 webcd 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).
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: standardruntime: nodejs14handlers: - 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: standardruntime: nodejs14handlers: - 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/webcd build/webgcloud 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.
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.
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:
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):
git clone https://github.com/GoogleCloudPlatform/cloud-builders-communitycd cloud-builders-community/fluttergcloud 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 -yRUN 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 channelARG channel=stableRUN flutter channel $channel# Perform a flutter upgradeRUN flutter upgradeENTRYPOINT [ "flutter" ]
This configuration is available in our Github repository as well, so you can simply run:
# from repository rootcd cloud-builder-fluttergcloud 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: bashtimeout: 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:
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
Want to know more?