
Cloud
Learning Level
Google App Engine is a fully managed platform for building web applications and APIs. Unlike Cloud Run which requires containerization, App Engine deploys directly from source code and handles all infrastructure management automatically.
By the end of this lesson, you'll understand:
App Engine is a Platform as a Service (PaaS) that:
| Feature | Standard | Flexible |
|---|---|---|
| Deployment | From source code | From Docker image or source |
| Cold Starts | 1-10 seconds | 30+ seconds |
| Scaling | Automatic, to zero | Automatic with minimum instances |
| Supported Languages | Node.js, Python, Java, Go | Any language in Docker |
| Local Disk | No (read-only FS) | Yes (temporary) |
| Cost Model | Pay per instance hour | Pay per instance + CPU/memory |
| Background Tasks | Limited (10 minutes) | Full support |
| Best For | Web apps, APIs | Complex apps, custom runtimes |
For beginners: Standard environment is simpler and cheaper.
# Initialize a GCP project for App Engine
gcloud app create --project=my-project --region=us-central
# Verify initialization
gcloud app describeThe `app.yaml` file tells App Engine how to run your application:
# app.yaml - Basic configuration
runtime: nodejs18
env: standard
# Service name (default service)
service: default
# Environment variables
env_variables:
NODE_ENV: "production"
API_KEY: "your-api-key"
# Handlers define URL routing
handlers:
- url: /.*
script: auto
# Automatic scaling configuration
automatic_scaling:
min_instances: 1
max_instances: 10
target_cpu_utilization: 0.65
target_throughput_utilization: 0.75Node.js Express Application:
// server.js
const express = require('express');
const app = express();
// Health check endpoint (required by App Engine)
app.get('/_ah/health', (req, res) => {
res.status(200).send('OK');
});
// Liveness check endpoint
app.get('/_ah/liveness', (req, res) => {
res.status(200).send('OK');
});
// Main endpoints
app.get('/', (req, res) => {
res.send('Hello from App Engine!');
});
app.get('/api/users', (req, res) => {
res.json([
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' }
]);
});
app.get('/api/users/:id', (req, res) => {
const userId = req.params.id;
res.json({ id: userId, name: `User ${userId}` });
});
app.post('/api/users', express.json(), (req, res) => {
const newUser = req.body;
res.status(201).json({ ...newUser, id: 3 });
});
// Error handling
app.use((err, req, res, next) => {
console.error(err);
res.status(500).json({ error: 'Internal server error' });
});
// Start server on PORT environment variable
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
console.log(`Server listening on port ${PORT}`);
});package.json:
{
"name": "app-engine-app",
"version": "1.0.0",
"description": "Simple App Engine application",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
},
"dependencies": {
"express": "^4.18.2"
},
"devDependencies": {
"nodemon": "^3.0.1"
}
}# Deploy to App Engine
gcloud app deploy
# View deployment details
gcloud app describe
# Open application in browser
gcloud app browse
# View recent deployments
gcloud app versions list --service=default# app.yaml - With environment variables
runtime: nodejs18
env: standard
env_variables:
NODE_ENV: "production"
LOG_LEVEL: "info"
DATABASE_URL: "cloudsql://localhost/mydb"
# For sensitive values, use Secret Manager
env_variables:
SECRET_KEY: "sm://my-secret-key"Access environment variables in code:
const databaseUrl = process.env.DATABASE_URL;
const nodeEnv = process.env.NODE_ENV;
const port = process.env.PORT || 8080;
console.log(`Running in ${nodeEnv} mode`);# app.yaml - With static files
runtime: nodejs18
env: standard
handlers:
# Serve static assets
- url: /static
static_dir: static
# Serve images
- url: /images
static_dir: public/images
# Route everything else to application
- url: /.*
script: auto# Add custom domain
gcloud app custom-domains create example.com
# Configure DNS records
# Add CNAME record: ghs.googlehosted.com
# Or A record: 216.58.217.46
# List domains
gcloud app custom-domains listApp Engine supports multiple services (microservices):
# api-service/app.yaml
runtime: nodejs18
env: standard
service: api
handlers:
- url: /api/.*
script: auto
automatic_scaling:
min_instances: 2
max_instances: 20# web-service/app.yaml
runtime: nodejs18
env: standard
service: web
handlers:
- url: /.*
script: auto
automatic_scaling:
min_instances: 1
max_instances: 10Deploy multiple services:
# Deploy API service
cd api-service
gcloud app deploy
# Deploy web service
cd ../web-service
gcloud app deploy
# Route traffic by domain or path
# api.example.com → api service
# example.com → web service// server.js - Cloud SQL connection
const mysql = require('mysql2/promise');
// Connection pool
const pool = mysql.createPool({
host: '/cloudsql/my-project:us-central1:my-database',
user: 'app-user',
password: process.env.DB_PASSWORD,
database: 'production',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
// Use connection in endpoint
app.get('/api/products', async (req, res) => {
const connection = await pool.getConnection();
try {
const [rows] = await connection.execute('SELECT * FROM products');
res.json(rows);
} catch (error) {
console.error('Database error:', error);
res.status(500).json({ error: 'Database error' });
} finally {
connection.release();
}
});app.yaml configuration for Cloud SQL:
runtime: nodejs18
env: standard
env_variables:
DB_PASSWORD: "sm://db-password"
# For Flexible environment
cloud_sql_instances:
- "my-project:us-central1:my-database"# View recent logs
gcloud app logs read
# View logs in real-time
gcloud app logs read -f
# View logs for specific service
gcloud app logs read --service=api
# Export logs to Firestore
gcloud app logs read --limit=1000 > logs.txt// server.js - Structured logging
function logStructured(severity, message, data = {}) {
const logEntry = {
severity,
message,
timestamp: new Date().toISOString(),
...data
};
console.log(JSON.stringify(logEntry));
}
app.use((req, res, next) => {
const startTime = Date.now();
res.on('finish', () => {
const duration = Date.now() - startTime;
logStructured('INFO', 'HTTP Request', {
method: req.method,
path: req.path,
status: res.statusCode,
duration_ms: duration,
user_agent: req.get('user-agent')
});
});
next();
});# View request count and latency
gcloud monitoring read-time-series \
--filter='resource.type="app_engine_app" AND metric.type="appengine.googleapis.com/http/request_count"'
# View error rate
gcloud monitoring read-time-series \
--filter='resource.type="app_engine_app" AND metric.type="appengine.googleapis.com/http/server_errors"'# app.yaml - Python runtime
runtime: python39
env: standard
handlers:
- url: /.*
script: automain.py:
from flask import Flask, jsonify
app = Flask(__name__)
@app.route('/')
def hello():
return 'Hello from App Engine!'
@app.route('/api/users')
def get_users():
return jsonify([
{'id': 1, 'name': 'Alice'},
{'id': 2, 'name': 'Bob'}
])
@app.route('/api/users/<int:user_id>')
def get_user(user_id):
return jsonify({'id': user_id, 'name': f'User {user_id}'})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080, debug=False)requirements.txt:
Flask==2.3.2
Werkzeug==2.3.6# app.yaml - Go runtime
runtime: go119
env: standard
handlers:
- url: /.*
script: automain.go:
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
)
func main() {
http.HandleFunc("/", helloHandler)
http.HandleFunc("/api/users", usersHandler)
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
log.Printf("Server listening on port %s", port)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil))
}
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello from App Engine!")
}
func usersHandler(w http.ResponseWriter, r *http.Request) {
users := []map[string]interface{}{
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Bob"},
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(users)
}# app.yaml - Scaling configuration
runtime: nodejs18
env: standard
automatic_scaling:
min_instances: 1 # Minimum instances always running
max_instances: 50 # Maximum instances for scaling
target_cpu_utilization: 0.65 # Scale up when CPU > 65%
target_throughput_utilization: 0.75 # Scale up when throughput high
min_pending_latency: 30ms # Scale up with pending requests
max_pending_latency: 100ms # Scale down if latency drops
min_idle_instances: 1 # Keep at least 1 warm
max_concurrent_requests: 50// server.js - Warm-up request handling
app.get('/_ah/warmup', (req, res) => {
// Initialize database connections
// Load caches
// Perform startup tasks
res.status(200).send('Warmed up');
});Always implement health check endpoints:
// Health endpoint for load balancing
app.get('/_ah/health', (req, res) => {
res.status(200).send('OK');
});
// Readiness endpoint (check dependencies)
app.get('/_ah/readiness', (req, res) => {
// Check database connection
// Check external service availability
res.status(200).json({ status: 'ready' });
});# app.yaml - Request timeout
runtime: nodejs18
env: standard
# Default is 25 seconds for Standard, higher for Flexible
timeout: 30s// Reuse database connections
const pool = mysql.createPool({
connectionLimit: 10,
queueLimit: 0,
enableKeepAlive: true,
keepAliveInitialDelayMs: 0
});// Handle graceful shutdown
process.on('SIGTERM', async () => {
console.log('SIGTERM received, shutting down gracefully');
// Close database connections
await pool.end();
// Close HTTP server
server.close(() => {
console.log('Server closed');
process.exit(0);
});
});# 1. Test locally
npm start # or python main.py, go run main.go
# 2. Check configuration
cat app.yaml
# 3. Deploy to App Engine
gcloud app deploy --version=v1
# 4. Verify deployment
gcloud app versions list
gcloud app describe
# 5. Test deployed application
gcloud app browse
# 6. Monitor logs
gcloud app logs read -f
# 7. Traffic splitting (canary deployment)
gcloud app services set-traffic default \
--splits=v1=0.8,v2=0.2
# 8. Rollback if needed
gcloud app services set-traffic default \
--splits=v1=1.0App Engine Standard pricing includes:
Tips for cost optimization:
Explore advanced patterns with App Engine Scaling & Performance, or learn about Firebase Hosting for static sites and single-page applications.
Resources
Ojasa Mirai
Master AI-powered development skills through structured learning, real projects, and verified credentials. Whether you're upskilling your team or launching your career, we deliver the skills companies actually need.
Learn Deep • Build Real • Verify Skills • Launch Forward