Hosting apps in the cloud with Google App Engine in 2024

wescpy

Wesley Chun (@wescpy)

Posted on October 22, 2024

Hosting apps in the cloud with Google App Engine in 2024

TL;DR:

If you're looking to put code online today, you have many options, with a VM or Kubernetes as the best choice for some. Others with less code may consider (AWS) Lambda. There are also options where developers don't have to consider traditional servers... these are known as serverless (compute) platforms. Google Cloud (GCP) offers several serverless options, and while Google (and I) will encourage developers to go straight to Cloud Run, it would be remiss not to start the conversation with their "OG" serverless platform, App Engine, as it still does a few things that Cloud Run can't or won't do. Cloud Run will be covered in future posts.

Serverless computing with Google

[IMAGE] Serverless computing with Google

Introduction

Welcome to the blog focused on using Google APIs, developer tools, and platforms, primarily from Python, but also sometimes Node.js. Here, you'll see me cover everything from to AI/ML on Google Cloud/GCP to Google Maps to YouTube to Gemini, and also nut-n-bolts like API key, and OAuth client ID credentials, especially for Workspace/GWS APIs like Google Docs and Google Sheets. The aim is to provide something for everyone.

The purpose of this post is to directly address the question, "Where can I host my app in the cloud?" More specifically, "How can I do so with the least amount of setup and friction?" The answers to both will almost always point to serverless, a subset of cloud computing focused on logic-hosting.

Serverless computing and logic-hosting

"Logic" means any code, whether a short code snippet to a web app to a container running a service. So "logic-hosting" is an all-encompassing term that includes app-hosting (hosting your apps in the cloud), function-hosting (hosting your functions in the cloud; FaaS), and container-hosting (hosting your containers in the cloud; CaaS).

All of that hosting can be accomplished with serverless computing, the ability to host that logic without thinking about or being concerned with servers, virtual machines or bare-metal. It's a bit of a misnomer because of course there are servers, but they've been abstracted away from users. Developers only need to focus on building their solutions, not what they run on. Learn more in the serverless post I'm about to introduce below.

Introduction

Google offers various serverless platforms for developers, and I introduce four (4) of them in the "Broader Perspective of Serverless" post. The majority of the platforms covered are offered by GCP, and I want to dive into each. Setting aside the use cases of functions and microservices for Cloud Functions (GCF) and Cloud Run Functions (GCRF), the platforms most suitable for applications (web apps primarily) are Google App Engine (GAE) and Cloud Run (GCR).

While Google and I encourage developers to start with GCR, GAE may still be the right tool for a certain class of users who prefer a bit less sophistication and core "building blocks" for building apps. GAE still has some features not available in GCR and is a pure PaaS solution while GCR falls between the PaaS and IaaS categories of cloud computing, meaning you have to do a few more things (like bundle a web server and tell GCR how to start your app) whereas it's taken care of by GAE.

With this in mind, I feel it's a better approach to start with GAE before diving into GCR, so here we are. The intention of the rest of the post is to show Python (3) and Node.js developers how to get a "Hello World!" app up-and-running on GAE with the least amount of friction. I'll then do the same with GCR and the functions/FaaS platforms in upcoming posts.

āš ļø ALERT: Cost: billing required (but free?!?)

While many Google products are free to use, GCP products are not. In order to run the sample apps, you must enable billing and have active billing supported by a financial instrument like a credit card (payment method depends on region/currency). If you're new to GCP, review the billing and onboarding guide. Billing wasn't mandatory to use GAE in the past but became a requirement in 2019.

The good news is that GAE (and other GCP products) have an "Always Free" tier, a free daily or monthly usage before incurring charges. The GAE pricing and quotas pages have more information. Furthermore, deploying to GCP serverless platforms incur minor build and storage costs.

Cloud Build has its own free quota as does Cloud Storage (GCS). For greater transparency, Cloud Build builds your application image which is than sent to the Cloud Container Registry (CR) or Artifact Registry (AR), its successor. Storage of that image uses up some of that GCS quota as does network egress when transferring that image to the service you're deploying to. However you may live in a region that does not have a free tier, so monitor storage usage to minimize potential costs. (Check out storage use and delete old/unwanted build artifacts via the GCS browser.)

With the above said, you may qualify for credits to offset any costs of using GCP. If you are a startup, consider applying for the GCP for Startups program grants. If you are a higher education student, faculty member, researcher, or are an education technology ("edtech") company, explore all the GCP for education programs.

Getting started

Let's see how easy it is to get started with GAE. The shortest/smallest app or "MVP" (minimally-viable product) requires just three (3) files:

  1. GAE configuration
  2. 3rd-party requirements
  3. Main application

Both samples are available in the repo for this post. Let's walk through each code sample, run locally, then do some GCP setup and deploy to GAE; Python first.

Python

The GAE configuration file is (always) named app.yaml (repo link), and while many options are available, an MVP only requires specifying the language runtime ID. In this sample app, I set it to Python 3.12:

runtime: python312
Enter fullscreen mode Exit fullscreen mode

Python's requirements.txt file (repo link) is used for specifying all 3rd-party libraries, and in this case, it's just the Flask micro web framework:

flask
Enter fullscreen mode Exit fullscreen mode

Name the main application file anything you want; I settled on main.py (repo link), shown here:

from flask import Flask

app = Flask(__name__)

@app.route('/')
def root():
    return 'Hello World!'

# local-only
if __name__ == '__main__':
    import os
    app.run(debug=True, threaded=True, host='0.0.0.0',
            port=int(os.environ.get('PORT', 8080)))
Enter fullscreen mode Exit fullscreen mode

The Flask library is imported, then the Flask app instantiated. The only route in this app is to '/' which returns 'Hello World!' from the (default) GET request (browser, curl, etc.). You can name route functions anything you like, such as main() or index() -- I often use root(). The last few lines are only for running this app locally... drop this section entirely if only deploying to the cloud. It uses the os module to read the PORT environment variable and fires up the Flask "devserver" (development server) on port 8080 if PORT isn't set.

Now run the script to test the app locally; your (terminal) output should look something like this:

$ python3 main.py
 * Serving Flask app 'main'
 * Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on all addresses (0.0.0.0)
 * Running on http://127.0.0.1:8080
 * Running on http://172.20.20.20:8080
Press CTRL+C to quit
 * Restarting with watchdog (fsevents)
 * Debugger is active!
 * Debugger PIN: 135-387-333
Enter fullscreen mode Exit fullscreen mode

With a running server, point a web browser to http://localhost:8080:

[IMAGE] "Hello World!" sample app running locally (Mac)

Ā 

In your terminal, you'll see log entries for each HTTP request:

127.0.0.1 - - [05/Dec/2023 15:42:55] "GET / HTTP/1.1" 200 -
Enter fullscreen mode Exit fullscreen mode

To exit the devserver, issue a ^C (Control-C) on the command-line.

Node.js

Now, let's turn to the Node.js version, which looks quite similar, starting with the config file, same name: app.yaml (repo link) but specifying Node 20:

runtime: nodejs20
Enter fullscreen mode Exit fullscreen mode

The Node.js 3rd-party requirements are found in the package.json file (repo link). The Python sample relies on the Flask web framework, and similarly, this Node.js app uses the Express.js framework. The package.json file looks like this:

{
  "name": "helloworld-nodejs",
  "version": "0.0.1",
  "description": "Node.js App Engine sample app",
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "author": "@wescpy",
  "license": "Apache-2.0",
  "dependencies": {
    "express": "^4.17.1"
  }
}
Enter fullscreen mode Exit fullscreen mode

Like with Python, call your main application anything you like. Typically with Node.js, they're named app.js, main.js, or what I picked: index.js (repo link). Here are its contents:

const express = require('express');
const app = express();

app.get('/', (req, rsp) => {
  rsp.send('Hello World!');
});

const PORT = process.env.PORT || 8080;
app.listen(PORT, () =>
  console.log(`Listening on port ${PORT}`)
);
module.exports = app;
Enter fullscreen mode Exit fullscreen mode

App instantiation is followed by the main (GET) handler returning "Hello World!" regardless of request content. The rest sets up the server on the designated PORT and exports the app. Unlike Python which automatically spawns a web server on GAE, Node.js requires you to run your own, hence why this code isn't enclosed in an if block like Python.

Run npm install to install all the required packages, resulting in the expected node_modules folder and package-lock.json file. Express doesn't log individually-received requests -- developers have to add middleware to see HTTP requests, so after starting the app with npm start to test the app locally, the only output you'll see until you quit the server looks like this (unlike Flask which dumps user requests to the console):

$ npm start

> helloworld-nodejs@0.0.1 start
> node index.js

Listening on port 8080
Enter fullscreen mode Exit fullscreen mode

Hit this app with your web browser at http://localhost:8080 and observe similar "Hello World!" output like with the Python app. Press ^C (Control-C) to quit the Express server.

If you prefer to work with modern ECMAScript modules, quick require-to-import and module export tweaks result in the equivalent index.mjs (repo link) ESM file:

import express from 'express';
const app = express();

app.get('/', (req, rsp) => {
  rsp.send('Hello World!');
});

const PORT = process.env.PORT || 8080;
app.listen(PORT, () =>
  console.log(`Listening on port ${PORT}`)
);
export default app;
Enter fullscreen mode Exit fullscreen mode

Deploying apps to App Engine

Now that you have something working locally, a more exciting next step is to run the app in the cloud and observe how it's accessible globally. Let's start with some GCP setup.

Google Cloud SDK

The gcloud command-line tool (CLI) is required to deploy apps to GAE. It comes with the Google Cloud SDK (software development kit). If you already have it installed, skip to the next section. If you're new to GCP, follow the instructions to install the SDK and learn a few gcloud commands to get comfortable using it. If you simply want to install the SDK, see these instructions instead. After installation, run gcloud init to initialize it -- you may be prompted to login.

Google Cloud project

A cloud project is required to manage all the resources, e.g., APIs, compute, and storage, used for an app. Create a new one or reuse an existing project. Then assign a billing account to that project if you haven't already.

Each project can create at most one GAE app, and it gets a free domain name: PROJECT_ID.REG.appspot.com where PROJECT_ID is the project ID (duh) while REG is an abbreviation for the assigned region.

For your convenience, set a default project with this command: gcloud config set project PROJECT_ID. If you don't set a default, you'd have to pass it in with --project-id PROJECT_ID each time you run a gcloud command. Otherwise you'll be prompted for the project ID.

šŸ“ Optional Python-related installs
Python developers have a few additional, optional installations if they're of interest to you.

  1. App Engine SDK (Python 3): If you have an existing Python 2 app using the GAE bundled services and considering a migration to Python 3, this is for you. The App Engine (services) SDK (not to be confused with the GCP SDK described above) gives Python 3 apps access to those bundled services bridging Gen1 apps to Gen2. Install it with: pip install appengine-python-standard (or pip3).
  2. GCP App Engine "extras": There are a pair of additional, (very) optional GCP components. The app-engine-python component contains the local GAE devserver (not to be confused with the Flask or Express devservers) if you want to simulate bundled services locally. Another one is app-engine-python-extras, which has code for Python 2 apps using built-in libraries. Since 2.x apps can no longer be deployed, you wouldn't use this. Regardless, install one or both components with a command like this: gcloud components install app-engine-python-extras app-engine-python

Final setup

Once you've confirmed you've completed these basic tasks, you should be ready to deploy your app:

  1. Install GCP SDK (includes gcloud CLI)
  2. Create new or reuse existing GCP project
  3. Enable billing & assign billing account
  4. Run gcloud init
  5. (optional) Run gcloud config set project PROJECT_ID
  6. (optional) Any additional installs (see sidebar above)

Deploy app to App Engine

Before deploying your sample Python or Node.js sample app, a few words on regions. Here are the key points all developers should know:

  1. Every GAE app is hosted in a GCP region.
  2. Regardless of which region it is hosted in, your app is accessible globally.
  3. When deploying an app the first time, you'll be prompted to set your GAE app's region.
  4. You'll only set the region once because it's a permanent choice (see sidebar below).
ā— App Engine region cannot be modified!
Every Cloud project can only have one App Engine app, and that app can only ever belong to one region. When assigned, that decision is permanent and cannot be changed, so choose wisely. Rest assured however that regardless of where your app is hosted, it's still reachable globally. Choose a region where you expect most of your users to be. If you insist on another region, you'll have to create another GAE app with another project. One advantage of newer GCP serverless platforms like GCR is that you can have more than one and they can serve different regions.

Let's take a look at the deployment process... run: gcloud app deploy. If you didn't set your project ID with gcloud earlier, pass it in with --project-id PROJECT_ID here or enter when prompted. You'll then be prompted to select a region. Your output will resemble this:

$ gcloud app deploy
Reauthentication required.
Please enter your password:
You are creating an app for project [PROJECT_ID].
WARNING: Creating an App Engine application for a project is irreversible and the region
cannot be changed. More information about regions is at
<https://cloud.google.com/appengine/docs/locations>.

Please choose the region where you want your App Engine application located:

 [1] asia-east1    (supports standard and flexible)
 [2] asia-east2    (supports standard and flexible and search_api)
 [3] asia-northeast1 (supports standard and flexible and search_api)

. . .

 [21] us-west2      (supports standard and flexible and search_api)
 [22] us-west3      (supports standard and flexible and search_api)
 [23] us-west4      (supports standard and flexible and search_api)
 [24] cancel
Please enter your numeric choice:  19

Creating App Engine application in project [PROJECT_ID] and region [us-east4]....done.
Enter fullscreen mode Exit fullscreen mode

Because your region choice is permanent, you'll never be prompted for it again. The rest of the output shown below is what you'll see next for your first and all subsequent deployments:

Services to deploy:

descriptor:                  [/private/tmp/app.yaml]
source:                      [/private/tmp]
target project:              [PROJECT_ID]
target service:              [default]
target version:              [20231205t154433]
target url:                  [https://PROJECT_ID.REG.appspot.com]
target service account:      [PROJECT_ID@appspot.gserviceaccount.com]

Do you want to continue (Y/n)?

Beginning deployment of service [default]...
ā•”ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•—
ā• ā• Uploading 3 files to Google Cloud Storage                ā•ā•£
ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•
File upload done.
Updating service [default]...done.
Setting traffic split for service [default]...done.
Deployed service [default] to [https://PROJECT_ID.REG.appspot.com]

You can stream logs from the command line by running:
  $ gcloud app logs tail -s default

To view your application in the web browser run:
  $ gcloud app browse
Enter fullscreen mode Exit fullscreen mode

On the first deployment, all three files are new, so that is what the build system uploads. On subsequent deployments, only new and modified files are uploaded. Once deployed, visit the app at: https://PROJECT_ID.REG.appspot.com, a real online address, and you'll see the same "Hello World!" page as before:

[IMAGE] "Hello World!" sample app running globally (PC)

Ā 

Now that you've successfully deployed a sample Python or Node.js app and have seen it running on Google Cloud, continue to experiment by changing the sample app and redeploying, or uploading one of your "real" apps. Regardless of next steps, you've now learned one place you can run your apps in the cloud, and nowhere in the discussion above did you consider allocating, configuring, or paying for a server.

Summary and next steps

Congratulations on taking your first step in learning about Google's original serverless platform, App Engine. In addition to web apps, GAE can also host mobile backends. A web UI is optional, and GAE only needs to receive HTTP GET and POST requests, as long as your app has the appropriate handlers for such requests. Overall, is serverless right for you? And what other platforms does GCP offer?

Where should you run your app?

Serverless is the right solution for developers who want their CSP (cloud services provider) to do most of the heavy-lifting in terms of app-hosting and deployment. It is suitable for apps where the traffic is unpredictable or infrequent (say for student projects, small business websites, mobile backends, etc.), including short-lived apps with extreme traffic, say for a royal wedding!

Apps that get steady, continuous traffic, requiring it be available 24x7, are likely best served with VM-based solutions like Google Compute Engine (GCE) or Google Kubernetes Engine (GKE) because the cost of serverless will outgrow its benefits. However, if you're just getting off the ground, serverless helps you get there first, or at least, faster. More on serverless non-use cases can be found in the other serverless post.

Newer serverless platforms > GAE

If serverless sounds right for you, GAE is one option, but GCP has two others: GCF and GCR, both newer than GAE and part of a "Serverless 2.0" generation of products. In late summer 2024, the 2nd-generation of Cloud Functions was rebranded as Cloud Run Functions (GCRF). The newer platforms extend from GAE's original app-hosting mission to include functions/microservices and containers.

When you don't have an entire app or don't want/need to worry about full-stack or (LAMP, MEAN, etc.) stacks at all, function-hosting is likely the tool for you. These scenarios cover shorter snippets of code to perform specific tasks or microservices in a multi-tiered application, or basic cloud-based mobile backend functionality.

Modern software applications are now shipped as containers. Organizing app code and resources helps isolate dependencies, coordinate versions, and serves the purpose of providing a "shopping bag" for what apps depends on. In the past, developers had to decide between the flexibility of containers versus the convenience of serverless. This "problem" is directly addressed by GCR which runs containers in a serverless way. Furthermore, those with modern AI/ML workloads will be happy to know that GCR supports GPUs serverlessly.

Overall, the newer platforms are generally more flexible and have newer features than GAE, giving you the ability to have multiple (GCF) functions or (GCR) services vs. one GAE app per project. Both GCF and GCR can be deployed to different regions too, which can be critical for some applications.

GAE > Newer serverless platforms

With all of the advantages of GCF, GCR, and GCRF, what can GAE still do better? The bundled services are a big part of the picture. Don't most apps need a database, caching server, external task execution, large blob/file storage, etc? And what if they're (mostly) free and highly-scalable? Yep, that's the promise that the original App Engine team envisioned. The newer serverless platforms require use of standalone GCP services, each of which has their own pricing, or your own best-of-breed options if you prefer or if GCP doesn't have an exact replacement.

Additionally, GAE infrastructure features an automatic static content service. This free pseudo-CDN (content delivery network) gives you edge-caching without having to set up your own CDN, critical to rendering assets to your users much faster than if served by your application. These are just a few things GAE still does better, so if they're less important for your app(s), you'll likely be better served by the newer serverless platforms.

Billing epilogue

If you want to keep the sample GAE app up and working-as-intended to continue learning and experimenting, great. However, recall it's not a free service, and while you're not likely to incur any charges, the longer you run it, and the more you deploy new versions, this will eventually lead to billable usage. There are several options to minimize getting charged.

One option is to "pause" your app so as to not get billed for inadvertent usage, especially if robots, hackers, or other online scanners discover your app. To do this, disable your app. When you're ready to continue playing with GAE, re-enable it. While your app is disabled and won't incur charges, be aware other resources used in your Cloud project may accrue charges.

Let's say you deployed your app numerous times, with accrued images and other build files exceeding the free tier on GCS or the registries (CR/AR). (In my experience, the registries accrue costs faster than GCS.) These will continue to bill you even with a disabled app, so be sure to tidy both up in addition to disabling your app to avoid (bad) surprises. (Also review the earlier sidebar on costs if you haven't already!)

On the other hand, if you're not going to continue with GAE or this GCP project, delete everything forever to avoid possible billing. To do that, shutdown your project.

Conclusion

If you're a Python, Java, Go, PHP, Ruby, or Node.js developer curious about GAE and made it this far, you now know what the platform is all about and how to deploy a sample app to the cloud. While new features are primarily going into the newer serverless platforms, GAE is still fulfilling its purpose of providing a serverless app-hosting service with autoscaling properties. GAE apps using bundled services can now migrate from Gen1 to Gen2 with much less friction and upgrade to more recent language versions.

When ready to migrate off the bundled services, developers have additional options of staying on GAE (Gen2), breaking up large apps into multiple microservices for GCF or GCRF, or containerizing their apps for GCR. Furthermore, apps that don't use GAE bundled services can also be migrated to "serverful"/VM-based platforms like GCE or GKE.

If you found an error in this post, bug in the code, or have a topic you want me to cover in the future, drop a note in the comments below or file an issue at the repo. If you have an older GAE app and need advice on possible next steps, whether upgrading from Python 2 to 3, migrating off the bundled services, or shifting to other serverless platforms (GCF/GCRF or GCR), I may be able to help... check out https://appenginemigrations.com and submit a request there.

Thanks for checking out my blog... I hope you find this content useful. I enjoy meeting users on the road, so check if I am visiting your community soon... see the travel calendar on my consulting page.

References

Below are various resources related to this post which you may find useful.

Blog post code samples

Google App Engine (GAE) resources

GCP compute platforms (serverless and VM-based)

GCP serverless content by the author

  • Pick a GCP serverless platform video
  • How to design serverless apps video
  • Exploring serverless with a nebulous app: Deploy the same app to App Engine, Cloud Functions, or Cloud Run blog post, video, code repo, socials

Google App Engine (GAE) historical references

Other GAE content by the author

  • GAE (Gen1 & early Gen2) 2024 deprecation "what you need to know" blog post
  • "Rapid cloud development using App Engine for the Cycle Hire Widget app" blog post -- Building London's bike-share app with GAE case study
  • "Change the world in 10 lines of code!" video -- Python GAE (inbound) Mail intro


^ -- Optional; only provides platform background information
Ā 
DISCLAIMER: I was a member of the GAE product team 2009-2013 and 2020-2023. While product information is as accurate as I can find or recall, the opinions are my own.



WESLEY CHUN, MSCS, is a Google Developer Expert (GDE) in Google Cloud (GCP) & Google Workspace (GWS), author of Prentice Hall's bestselling "Core Python" series, co-author of "Python Web Development with Django", and has written for Linux Journal & CNET. He runs CyberWeb specializing in GCP & GWS APIs and serverless platforms, Python & App Engine migrations, and Python training & engineering. Wesley was one of the original Yahoo!Mail engineers and spent 13+ years on various Google product teams, speaking on behalf of their APIs, producing sample apps, codelabs, and videos for serverless migration and GWS developers. He holds degrees in Computer Science, Mathematics, and Music from the University of California, is a Fellow of the Python Software Foundation, and loves to travel to meet developers worldwide at conferences, user group events, and universities. Follow he/him @wescpy & his technical blog. Find this content useful? Contact CyberWeb if you may need help or buy him a coffee (or tea)!

šŸ’– šŸ’Ŗ šŸ™… šŸš©
wescpy
Wesley Chun (@wescpy)

Posted on October 22, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related