Hosting Flask servers on Firebase from scratch

Python devs 🐍! Rejoice 🎉! Firebase Hosting 🔥 supports dynamic server backends with Cloud Run 🏃‍♂️! Alright. That’s enough emojis. Time to dive into the details.

David East
Firebase Developers
8 min readAug 27, 2019

--

Wait; isn’t Firebase Hosting just for static web pages?

Nope! Back in 2017 Firebase integrated Hosting with Cloud Functions to allow you to dynamically generate content on the server and send it back through their Content Delivery Network (CDN). This allowed you two mix and match a strategy of serving dynamic pages and plain ol’ static pages.

At the time, however, these functions were limited to a Node.js runtime. Today, Cloud Run allows you to do so much more.

Any server backend with Cloud Run

With Firebase Hosting integrating with Cloud Run, you can generate content from anything that fits within a stateless Docker container. This means you can pick whatever language and server framework your heart desires.

This tutorial will show you how to get a Flask server working from scratch with Firebase Hosting and Cloud Run. Let’s get started!

Note: You don’t need to install Docker

Cloud Run is a serverless way to run Docker containers. However, this tutorial will use Cloud Build to build and store your container for Cloud Run. No need to worry if you don’t have Docker installed.

Step 0 — Project structure

Let’s get started off right with a simple project structure. You don’t need to create each file and folder upfront, but you can use this as a guide throughout the tutorial.

The split between server and static is important. We might be executing server code in some cases, but that doesn’t mean we want that code to serve our static files, too.

Step 1 — Setup a simple Flask server

Create an index.html file in the server/src/templates directory and paste in the following code:

Then open or create server/src/app.py and paste in the following code.

This is just a simple Flask server that renders a template in response to the index (/) endpoint .The Flask server will automatically look in the templates folder and look for the index.html file. The template renders "Flask + Firebase" with the server’s current time.

Now that you have the code for a simple Flask server, let’s get it working in a Docker container.

Step 2 — Creating a Dockerfile

Inside of server create a file called Dockerfile. No extension needed. If you’ve never worked with Docker before, think of Docker as a chef that builds delicious self-contained software environments and a Dockerfile as a recipe to run your server code.

Let’s go over each section.

  1. This sets the base image of the container. Your code will run on a version of Python 3.7.
  2. Install the needed dependencies to run the server: Flask and gunicorn.
  3. Copy the server’s source files into a folder within the container named app. Then set the working directory to that app folder.
  4. Set an environment variable for the port for 8080. Note: Cloud Run expects the port to be 8080.
  5. Run gunicorn bound to the 8080 port.

With the Dockerfile written, let’s deploy!

Step 3 — Create a Firebase project and enable billing

If you don’t have a project in mind, go to the Firebase Console and create a new project. Keep track of the project id, you’ll need this soon.

Cloud Run has a free tier

To use Cloud Run with Firebase Hosting you currently need billing enabled, which requires putting a credit card on file. However! That doesn’t mean you’re going to get charged. Cloud Run comes with a free tier. You’ll likely operate in the free tier unless you are using it on a production site or sending large amounts of traffic to your site all month long.

Cloud Run’s free tier at the time of this writing.
Cloud Run’s free tier at the time of this writing

With billing enabled, it’s time to let Cloud Build do the heavy work of building our server code into a Docker container.

Step 4 — Build the container with Cloud Build

To get Cloud Run to execute your container you need to store it in Google Cloud’s Container Registry (GCR). You can upload a local container, but that requires installing Docker, which this tutorial does not require. Instead you’ll send it to Cloud Build and it will build it and upload to GCR for you.

For this step you’ll need to install the Google Cloud CLI (a.k.a gcloud). Once you have it installed you can use it to initialize the SDK to authorize to your account.

After you have initialized everything, open a terminal to the server directory and run the following command:

Once you see a success message you’re ready to deploy to Cloud Run.

Step 5 — Deploy the container to Cloud Run

Inside the same server directory, run the following command to upload to Cloud Run.

You may be asked questions about enabling APIs and allowing unauthenticated access. Say yes to both. Don’t worry about the access; since this is a web server it needs to be publicly available.

After the deploy completes, you should see a service URL like this: https://flask-fire-239532hash-us-central-1.a.run.app. Paste that link into a new browser tab and you should see the bland "Flask + Firebase" text.

Now it’s time to set up Firebase Hosting to serve your static files and the content generated with Cloud Run.

Step 6 — Set up Firebase Hosting

To set up Firebase Hosting you’ll need the Firebase CLI installed via npm. You can do this locally, globally, or with even withnpx. For the sake of simplicity I’ll cover a local install (global installs can sometimes be messy).

Open a terminal to the root of the project directory and run the following commands:

This should create a node_modules folder with all kinds of dependencies in it. From here we can invoke the Firebase CLI to set up Hosting. From the root of the project run the following command:

This calls the locally installed Firebase CLI to set up Firebase Hosting within your project. You’ll be asked a few questions:

This sets up your app to deploy to the proper Firebase project. The second question sets up Firebase Hosting to deploy the static directory. Responding no to that last question will not configure re-writes for a SPA (single-page web application). You should see a firebase.json and .firebaserc in the root of your project.

Make sure no index.html exists!

The CLI may write a 404.html and an index.html in the static directory. If that happens make sure to delete the index.html. Firebase Hosting serves static files first. This means if index.html exists, it will not call out to the Cloud Run container. To delete then file, run the following command from the root of the project:

Add a style.css in the static directory

To add some style to your web app, create astatic/style.css file with the following contents:

With the project set up locally, we can hook up Cloud Run to Firebase Hosting.

Step 7 — Hook up Cloud Run to Firebase Hosting

Firebase Hosting calls out to Cloud Run when it finds a matching URL pattern rewrite in your firebase.json and no static file exists. This allows you to build your site with static and dynamic routes. Since this is a simple site, you’re going to serve every path through a re-write. You don’t need to worry about setting up routes for static assets since Firebase Hosting serves them first.

Open up your firebase.json and create a new re-write rule like below:

The "rewrites" section creates an array of rewrites with a single object. This object specifies that when a request comes in matching a "source" of "**" (which is everything), it will call out to Cloud Run to the container tagged "flask-fire" , which you previously deployed to Cloud Run.

Let’s serve this locally to see it work in action. Open a terminal to the root of your project and run the following command:

This will run your site on a port of locahost:5000. Note: While your static files are being served locally, the emulator is still making a call out to Cloud Run.

You’ll notice that the site still renders "Firebase + Flask" but it’s centered and has Lobster font! How lovely.

The next step is to set up custom caching control over your content in the CDN.

Step 8— Static and Dynamic

A huge advantage of using Cloud Run with Firebase Hosting is the control over the CDN cache. Firebase Hosting allows you to control how long a piece of content stays in the CDN cache via a Cache-Control header.

The s-maxage property tells Firebase Hosting to keep the content in the cache for 10 minutes. During this 10 minute period Firebase Hosting will skip running your server code in Cloud Run and serve the cache content directly.

What happens if I don’t set a Cache-Control header? If you don’t set a Cache-Control header (and no underlying system sets the header), Firebase Hosting will call out to Cloud Run each and every time.

Flask’s make_response make it easy to attach headers. Open up app.py and modify the index() route to the following:

The code above does the following:

  1. Create the template given the context.
  2. Create a response with the template.
  3. Attach a Cache-Control header to control store the content for a 10 minute period in the local CDN edge server. This time period is referred to as a Time To Live or TTL.

Let’s get to the fun part! Deploying!

Step 9 — Deploy to Firebase Hosting

First you’ll need to redeploy the container since changes were made to the server code. Run the following commands:

This build the container in Cloud Build and deploy to Cloud Run. Now run the Firebase deploy command

That’s it! You have deployed a Flask app on to Firebase Hosting and possibly learned a thing or two about Docker and CDNs along the way.

Finished!

You now have a deployed Flask app that only regenerates when the Cache-Control TTL has expired! The timestamp will stay the same until the TTL expires and the regeneration process begins again.

Send us your Flask apps!

So there it is— with just a few simple steps, you can create a dynamic web site hosted entirely on Firebase Hosting. Does this inspire you to build your own website using Firebase and Flask? If so, I’d love to see what you created! Drop me a comment below, or send me a tweet!

--

--

David East
Firebase Developers

Developer Advocate at Google. Working on the Firebases.