[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]