Cloud Functions and App Engine are both serverless platforms on Google Cloud, but they serve fundamentally different use cases and operate on distinct execution models.

Let’s see Cloud Functions in action. Imagine you want to resize an image every time it’s uploaded to a Cloud Storage bucket.

from google.cloud import storage
from PIL import Image
import io

def resize_image(event, context):
    """Triggered by a change to a Cloud Storage bucket."""
    file_data = event
    bucket_name = file_data['bucket']
    file_name = file_data['name']
    content_type = file_data['contentType']

    if not content_type.startswith('image/'):
        print(f"Skipping non-image file: {file_name}")
        return

    storage_client = storage.Client()
    bucket = storage_client.bucket(bucket_name)
    blob = bucket.blob(file_name)

    # Download the image
    image_bytes = blob.download_as_bytes()
    image = Image.open(io.BytesIO(image_bytes))

    # Resize the image
    new_width = 200
    new_height = image.height * (new_width / image.width)
    resized_image = image.resize((new_width, int(new_height)))

    # Upload the resized image
    output_blob = bucket.blob(f"resized_{file_name}")
    with io.BytesIO() as output_buffer:
        resized_image.save(output_buffer, format=image.format)
        output_blob.upload_from_file(output_buffer, content_type=content_type)

    print(f"Resized image {file_name} and saved as resized_{file_name}")

This Python function resize_image is deployed as a Cloud Function. It’s triggered by a google.cloud.storage.object.finalize event. When a new file lands in the specified bucket, this function spins up, downloads the image, resizes it, and uploads the new version back to the same bucket. It’s a single, discrete task executed in response to an event.

App Engine, on the other hand, is designed for hosting full-fledged web applications and APIs. Think of it as a managed platform for running your backend services. You deploy your application code, and App Engine handles the infrastructure, scaling, and traffic routing.

Consider a simple Python web application using Flask, deployed to App Engine:

# app.yaml
runtime: python39
entrypoint: gunicorn -b :$PORT main:app

# main.py
from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello():
    return 'Hello, world!'

if __name__ == '__main__':
    app.run(host='127.0.0.1', port=8080, debug=True)

When you deploy this, App Engine provisions instances to serve incoming HTTP requests. It automatically scales these instances up or down based on traffic. You’re not directly reacting to individual events like file uploads; you’re providing a continuously running service that accepts requests and sends back responses.

The core problem Cloud Functions solves is the execution of small, event-driven code snippets without managing servers. You pay only for the time your code is running, and it scales automatically from zero to handle spikes. This is perfect for tasks like background processing, data transformation, or integrating with other services.

App Engine addresses the need for a managed platform to host web applications. It abstracts away the complexities of server management, load balancing, and deployment pipelines. You focus on your application code, and App Engine ensures it’s available and scales appropriately. There are two environments: Standard and Flexible. Standard is more opinionated, with faster scaling and lower latency, but has restrictions on runtimes and libraries. Flexible offers more control and customizability but scales more slowly and has higher costs.

The exact levers you control for Cloud Functions are primarily the trigger type (HTTP, Pub/Sub, Cloud Storage, etc.), memory allocated, timeout duration, and environment variables. For App Engine, you configure the runtime, scaling parameters (min/max instances, scaling metrics), and network settings through the app.yaml file.

What most people don’t realize about Cloud Functions is that while they are often associated with simple, single-purpose tasks, they can be chained together to build complex workflows. A Cloud Function triggered by a Pub/Sub message can, in turn, publish a message to another Pub/Sub topic, which then triggers a second Cloud Function. This event-driven choreography, while requiring careful design, allows for sophisticated, scalable architectures without explicit orchestration services.

The next step in understanding serverless on Google Cloud is exploring Cloud Run, which bridges the gap between Cloud Functions and App Engine by allowing you to run containerized applications on a serverless platform.

Want structured learning?

Take the full Cloud-functions course →