As a backend developer, I often find myself collaborating closely with my frontend team to ensure the seamless deployment of web applications. Recently, our frontend team brought forth a pressing challenge that was slowing down our development workflow.

Problem Statement πŸ€”

They were responsible for managing a Flutter web application running on a Google Cloud Platform (GCP) virtual machine (VM). While the app itself was impressive, the deployment process left much to be desired.

The existing deployment workflow was a multi-step ordeal:

  1. Build the Flutter web app.
  2. Upload the app to the VM.
  3. Unzip the uploaded package.
  4. Extract the contents into the appropriate Apache directory.
  5. And sometimes, things went awry, causing deployment to drag on for far too long.

Initial Attempt with Docker Image Deployment 🐳

Our initial approach was to implement Docker image deployment on the VM using GitHub Actions. While this seemed promising, it quickly became apparent that it came with its own set of complexities and challenges.

It was clear that our initial attempt was far from the seamless deployment solution we were aiming for.

Cloud Run The Saviour ☁️

In our quest to find a more efficient deployment solution and streamline collaboration, we stumbled upon Cloud Runβ€”a game-changer in our deployment journey.

Here are some key features we harnessed:

Auto Deployment from GitHub πŸš€

Containerization Made Easy 🐳

Auto-Scaling for Efficiency βš–οΈ

Fetching Code from GitHub πŸ”„

Cost Efficiency πŸ’°

Robust Monitoring and Logging πŸ“Š

Solution - Implementation: 😜

Now let's jump into the actual solution which worked for us, adding all the templates that worked for us so you can also utilize the same or modify it according to your needs.

In this solution phase we will go through

Step 1: Setting Up Docker in Repo

# Stage 1: Build the application
FROM dart:stable AS build

# Install dependencies for Flutter
RUN apt-get update && apt-get install -y curl git unzip xz-utils zip libglu1-mesa

# Clone the Flutter repository
RUN git clone https://github.com/flutter/flutter.git /usr/local/flutter

# Set the Flutter path
ENV PATH="/usr/local/flutter/bin:/usr/local/flutter/bin/cache/dart-sdk/bin:${PATH}"

# Checkout the specific version of Flutter
RUN flutter channel stable
RUN flutter upgrade
RUN git -C /usr/local/flutter checkout 1f6bdb6

#above after checkout is commit hash which indicates the version of flutter
#to learn more about flutter versioning, please visit https://flutter.dev/docs/development/tools/sdk/releases?tab=linux

# Verify Flutter installation
RUN flutter doctor

# Set working directory
WORKDIR /app

# Copy over your app
COPY pubspec.* /app/
RUN flutter pub get
COPY . /app
RUN flutter pub get --offline
RUN flutter clean
RUN flutter build web --release --no-tree-shake-icons

# Stage 2: Create the runtime environment with Apache
FROM httpd:alpine

# Install gettext for envsubst
RUN apk add --no-cache gettext

# Copy the built app to the Apache server directory
COPY --from=build /app/build/web/ /usr/local/apache2/htdocs/

# Expose port
EXPOSE 8080

# Copy the Apache configuration template and startup script
COPY httpd.conf.template /usr/local/apache2/conf/httpd.conf.template

# Add a shell script to start Apache with the right PORT
COPY start-apache.sh /usr/local/bin/start-apache.sh
RUN chmod +x /usr/local/bin/start-apache.sh

# Start Apache using the startup script
CMD ["start-apache.sh"]

# this should go in directory with httpd.conf.template

ServerRoot "/usr/local/apache2"

Listen ${PORT}

LoadModule mpm_event_module modules/mod_mpm_event.so
LoadModule authn_file_module modules/mod_authn_file.so
LoadModule authn_core_module modules/mod_authn_core.so
LoadModule authz_host_module modules/mod_authz_host.so
LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
LoadModule authz_user_module modules/mod_authz_user.so
LoadModule authz_core_module modules/mod_authz_core.so
LoadModule access_compat_module modules/mod_access_compat.so
LoadModule auth_basic_module modules/mod_auth_basic.so
LoadModule reqtimeout_module modules/mod_reqtimeout.so
LoadModule filter_module modules/mod_filter.so
LoadModule mime_module modules/mod_mime.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule env_module modules/mod_env.so
LoadModule headers_module modules/mod_headers.so
LoadModule setenvif_module modules/mod_setenvif.so
LoadModule version_module modules/mod_version.so
LoadModule unixd_module modules/mod_unixd.so
LoadModule status_module modules/mod_status.so
LoadModule autoindex_module modules/mod_autoindex.so
LoadModule dir_module modules/mod_dir.so
LoadModule alias_module modules/mod_alias.so
LoadModule proxy_html_module modules/mod_proxy_html.so

<IfModule unixd_module>
User daemon
Group daemon
</IfModule>

ServerAdmin [email protected]

<Directory />
    AllowOverride none
    Require all denied
</Directory>

<Directory "/usr/local/apache2/htdocs">
    Options Indexes FollowSymLinks
    AllowOverride None
    Require all granted
</Directory>

<IfModule dir_module>
    DirectoryIndex index.html
</IfModule>

<Files ".ht*">
    Require all denied
</Files>

ErrorLog /proc/self/fd/2

LogLevel warn

<IfModule log_config_module>
    LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
    LogFormat "%h %l %u %t \"%r\" %>s %b" common
    CustomLog /proc/self/fd/1 common
</IfModule>

<IfModule alias_module>
    ScriptAlias /cgi-bin/ "/usr/local/apache2/cgi-bin/"
</IfModule>

<Directory "/usr/local/apache2/cgi-bin">
    AllowOverride None
    Options None
    Require all granted
</Directory>

<IfModule headers_module>
    RequestHeader unset Proxy early
</IfModule>

<IfModule mime_module>
    TypesConfig conf/mime.types
    AddType application/x-compress .Z
    AddType application/x-gzip .gz .tgz
</IfModule>

<IfModule mime_magic_module>
    MIMEMagicFile conf/magic
</IfModule>

Include conf/extra/proxy-html.conf

#!/bin/sh
# Substitute environment variables in Apache configuration
envsubst < /usr/local/apache2/conf/httpd.conf.template > /usr/local/apache2/conf/httpd.conf

# For debugging: print the PORT variable and the resulting httpd.conf
echo "PORT: $PORT"
# cat /usr/local/apache2/conf/httpd.conf

# Start Apache in the foreground
exec httpd -DFOREGROUND

Step 2: Setting Up Cloud Run

The Journey πŸš—

This blog captures our journey from a challenging deployment process to a streamlined and efficient solution using Cloud Run. With features like auto deployment, containerization, auto-scaling, and cost efficiency, Cloud Run has become a vital tool in our development arsenal. Say goodbye to deployment woes and embrace Cloud Run for a smoother development experience. β˜οΈπŸš€