[MUSIC PLAYING]
DAVID EAST: Hey, everyone.
Welcome to a new Firecast.
And today we're going to be using
Firebase Hosting and Cloud Run to host a Flask server.
So you might not know that Firebase Hosting
can support dynamic server code like Python.
And you can do that with Cloud Run.
And Cloud Run is a way to serverlessly run containers.
So if they can fit into a stateless Docker container,
you can hook that up with Firebase Hosting.
And you get all the benefits of Firebase Hosting CDN.
So let's learn how to do that and dive down into the laptop.
So here in my editor, I'm going to create two folders, a Server
folder and a Static folder.
Within Server I'm going to create a source
folder to hold my code.
And I'll create an app.py file for my Flask app.
So I'll import from Flask--
import Flask-- and create my Flask app with underscore name.
And now create my index route, so Def index.
And within here I'm just going to do something simple.
So I'll return hello world.
Now I want this to run.
So I'll first check if name is equal to underscore main.
And then now I can call app.run.
I'll set debug to true, the host to 0.0.0.0,
and the port I'll set to 8080.
But instead of hard coding it, I'm going to import from OS
and cast it to an int but call OS dot environ dot get port.
And if it doesn't exist, then supply 8080.
So I want to do more than just return hello world.
I actually want to do html.
So I'm going to import the render template function.
And I'll delete this and replace it with render template index
dot html.
And Flask by default will look for this template inside
of a templates folder.
So I'll go create index.html.
And then, boom.
I have this just simple little template that we can render.
So now I want to take all of this code.
And I can run it on a server.
So I could call Python 3 and then go to the app.py file.
But I don't want to do it this way.
I want to Dockerize it.
So to get started I'm going to create this Docker file.
And this Docker file is basically our recipe
for our server environment.
So I'm going to start by providing a base image,
so from Python 3.7.
And then from here, I can run a command
to install all of our dependencies.
So PIP install Flask and gunicorn.
Now I need to copy our source code into a folder
in the container-- so source to app--
and then set this container folder
as the working directory.
And from here I'm going to set the environment variable
for our port, which, if you remember,
we set that back here.
And now I'm going to run a command for gunicorn, where
I bind the ports, set up the workers to be 1
and threads to be 8, and then bind the app.
Something to note real quick is that the Firebase Hosting
and Cloud Run integration does not
operate within the Spark plan or the free tier.
You need to upgrade to the Blaze plan, which
requires a credit card.
However, we're going to be using Cloud Build and Cloud
Run, which do have their own free tiers.
So even if you enter a credit card,
you can still operate within the free tier without any charges.
And one more thing--
I promise just one more thing--
to get started, we need to use the Google Cloud
SDK, which you can install.
A Link in the description.
There's some quick starts.
It's pretty easy to get started.
Once you've set up the G Cloud SDK,
you'll need to log in with gcloud auth login.
But since I've done that, I'm going
to initialize the G Cloud init.
I'm going to create a new configuration,
enter in a name for it, specify that it's
my email that's doing these operations,
and then pick a project.
The next step is to build a container
and store it in Google Container Registry.
And then that way we can deploy it to Cloud Run.
To build it you'll need Docker locally.
But if you don't have Docker locally
or don't want to install it, you can actually
do it through Cloud Build.
First I need to cd into my folder with the Docker file.
And then now I'm going to call G Cloud Builds,
submit, give it a tag--
gcr.io slash my project ID, which is day-of--
and then I'm going to give it a container name, which--
mine is called Flask Fire.
I'm going to send that up.
And it's going to ask me if I need to enable some APIs,
which, yes, I do.
So we'll do that.
And then now we can deploy it to Cloud Run.
So G Cloud Run deploy.
Give it the image, which is the same as the tag.
So it would be project ID, which is day-of,
and then the container name, Flask Fire.
I'll submit that.
I need to choose that it's fully managed.
I'll have to enable the API.
And then for Firebase Hosting, we
need to make sure it's in US central one.
Confirm the name.
And allow unauthenticated invocations
because it's a web server.
That's going to kick off the deploy.
But once it's done, it's going to give us this link.
Paste that to the browser.
And you can see that we have our Flask plus Firebase page.
Now we have our serverless container up and running.
Let's hook it up to Firebase Hosting.
Hooking up Cloud Run to Firebase Hosting
means we need to install the Firebase CLI.
So I'm going to CD to my route and do an [INAUDIBLE] init.
And now I can install the Firebase CLI, so
NPMI-D Firebase tools.
So we'll wait for that to install.
And now that it's up and running,
I can go into my node modules.
And I can do a Firebase init hosting.
And this will take you through the process.
But I'm actually going to go and create it from scratch.
So what init hosting does is it creates
Firebase dot JSON file and a dot Firebase RC.
Within Firebase RC we can set up our list of projects.
You have to have a default. Mine's called day-of.
And now in Firebase dot JSON, I'm
going to create a hosting key.
And I'll tell hosting that my public folder is
my static folder.
And I will ignore some files, so my Firebase dot JSON,
any dot files, and definitely my node modules.
The goal is I want every single part of my site served
through Firebase Hosting.
But I want the dynamic parts generated through Cloud Run
and then served back through Firebase Hosting.
So the way it works is as a user requests the site.
Firebase Hosting checks to see if it's static or dynamic.
If it's dynamic it'll go out to Cloud Run,
generate, and then Firebase Hosting serves it back.
So for those paths that are dynamic,
I need to set a rewrite.
So I'm going to have a rewrites entry.
I'm going to set a source.
And this is going to be star star.
And that will match everything except if it's a static file.
Then I'll provide the run key.
And this has a property of service ID.
And this goes to our service ID, which
we've been calling flask-fire.
To see if everything's working I'm
going to run Firebase Serve.
And once that kicks off, it will run this on localhost5000.
So let's visit that.
And this is working.
But there's no styles.
I want my styles served statically
over Firebase Hosting.
So I'm going to open up the Static folder
and create a styles.css file.
Paste in these styles, which gives me some lovely lobster
font, a class to make it full screen,
stuff to center it, and just some big font.
Nothing too crazy.
And I'm going to apply these in my template.
So I first need to link out to the style sheet.
And keep in mind that we generate
this template on the server.
But we link it to this style sheet,
which is served statically, which is never generated.
And that's kind of cool.
It's a mix between static and dynamic.
So now I'll apply the classes, full screen and center.
And then let's make sure this H1 is nice and big.
And then I'm going to do some templating.
So I want to render what the server time is.
To pass in this context, I'm going to go to the app.py file.
And we can pass it within render template right here.
I'm going to import from time.
So we can format.
I'll create a function called format server time.
And then I will store the server time in a variable,
so time dot local time.
And then we can format it by calling time dot strftime.
And paste in a human-readable format.
And pass the server time as the variable.
And now I can set that as the context, so in a dictionary,
so server time, and we'll call it format server time.
And within here, we can pass the context and set
context equal to context.
So now that this has changed, I want
to rebuild this in Cloud Build.
So I'm going to cd into the server directory
where the Docker file is, call G Cloud builds submit.
Give it a tag of gcr.io slash my project
ID, which is day-of, and then flask-fire the service ID.
Once that finishes building, I'm now
going to send it out to Cloud Run.
So G Cloud Run deploy.
Give the image of gcr.io, the project ID day-of,
and then the service ID flask-fire,
and I need to say I want it fully managed.
And as you can see here, we can actually pass this with flags.
So the next time around, we'll make sure to do that.
But now I'm going to set my choice for US central 1
and then confirm my name.
Once this finishes deploying, I'm
going to cd back to my route so I can run Firebase serve.
This will spit it up on the localhost5000.
And then here we got our styles.
And if we refresh, we get a new timestamp
because it's being dynamically generated
on the server with Cloud Run and then served
through Firebase Hosting.
So the container is running the Flask server
that generates our content.
But we're missing out on the best part of the Firebase
hosting integration.
And that's the ability to control the CDN cache.
With Firebase Hosting, you get to set a caching header that
sets how long a CDN should hold the content before it goes back
to Cloud Run to regenerate.
And that means the user is going to get
the content delivered to them much faster when
it's in the CDN.
And setting this up is super easy.
To modify the response, I'm going to import the function
make_response.
And I'll create a template variable for render template.
And now I'll create a response variable
to pass in that template to make the response.
From here I can attach the headers.
So I'm going attach the cache control header
and now set a value.
We need to make sure that it's public so it's cache-able.
We're going to set a max age-- and that's for the browser--
300 seconds.
And then S max age, that's for the CDN.
That's 600 seconds.
Now we just need to go and return this response.
Let's cd into the server folder so we can submit
the built to Cloud Build.
Give it a tag, gcr.io slash day-of, slash flask-fire.
Submit that.
Now that the build is done, we can deploy it to Cloud Run.
I'll type G Cloud Run Deploy.
And this we'll provide the service name, flask-fire.
I'll give it the tag for region, so US central one.
The platform manage tag--
now I can pass the image, so gcr.io
slash day-of slash flask-fire.
And submit.
Once this finishes deploying, we can CD back up to the route
so I can deploy to Firebase hosting.
So I'll tap into my node modules been
and do Firebase deploy dash, dash only hosting.
And that's because our Cloud Run container has been deployed.
So it just needs to deploy with Firebase Hosting.
And that will go pretty quickly.
Now that it's done, we have our URL.
And here we can see we have the page.
But the magic part is that cache control, max age 300.
S max age 600.
And we can see that it was hit in the CDN cache.
And we have some cache hits.
So the best part about using Firebase Hosting and Cloud
Run together is right here.
It took only 25 milliseconds to load this page.
And that's because it's stored in a CDN.
So after the first time the container runs,
it will store that in the CDN for however
long you set that S max age header.
And this is a huge boost in performance
because we don't have to run the container each and every time
we load the page.
So with Firebase Hosting and Cloud Run,
you get an awesome mix of static and dynamic capabilities.
So that's everything you need to do to hook up Firebase
Hosting with Cloud Run.
And remember to set that S max age
directive on the cache control header,
because it's super important.
So let us know what you're building
with various hosting and Cloud Run
and what base Docker images are using
and what kind of apps and stuff are you doing because we
would love to know that.
Just let us know in the comments.
So that's everything we have for you today.
Don't forget to like and subscribe
because we'll be doing lots more of our Firebase Hosting
and Cloud Run videos.
And I will see you on the next episode.
[MUISC PLAYING]