GCP Cloud Run: Complete Containerized Application Guide 2026
in Development on Gcp, Cloud-run, Docker, Serverless
GCP Cloud Run: Complete Containerized Application Guide 2026
Google Cloud Run has emerged as the leading serverless container platform in 2026. This guide covers deploying, scaling, and managing containerized applications with Cloud Run.
Why Cloud Run?
- Container flexibility - Run any language, library, or binary
- Serverless scaling - Scale to zero, scale to thousands
- Pay per use - Charged only for actual CPU and memory usage
- Full managed - No cluster management required
- Knative compatible - Portable workloads
Getting Started
Prerequisites
# Install Google Cloud CLI
curl https://sdk.cloud.google.com | bash
# Initialize and authenticate
gcloud init
gcloud auth login
gcloud auth configure-docker
# Enable required APIs
gcloud services enable run.googleapis.com
gcloud services enable cloudbuild.googleapis.com
gcloud services enable artifactregistry.googleapis.com
Project Setup
# Create Artifact Registry repository
gcloud artifacts repositories create my-repo \
--repository-format=docker \
--location=us-central1
Building Container Images
Dockerfile Best Practices
# Dockerfile
FROM python:3.12-slim as builder
WORKDIR /app
# Install dependencies in builder stage
COPY requirements.txt .
RUN pip install --no-cache-dir --user -r requirements.txt
# Production image
FROM python:3.12-slim
WORKDIR /app
# Create non-root user
RUN useradd --create-home --shell /bin/bash appuser
# Copy dependencies from builder
COPY --from=builder /root/.local /home/appuser/.local
# Copy application code
COPY --chown=appuser:appuser . .
# Switch to non-root user
USER appuser
# Add local bin to PATH
ENV PATH=/home/appuser/.local/bin:$PATH
# Cloud Run sets PORT environment variable
ENV PORT=8080
EXPOSE 8080
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
# Run application
CMD ["gunicorn", "--bind", "0.0.0.0:8080", "--workers", "2", "--threads", "4", "app:app"]
Python Application
# app.py
from flask import Flask, jsonify, request
import os
app = Flask(__name__)
@app.route('/')
def index():
return jsonify({
'service': 'my-api',
'version': os.environ.get('K_REVISION', 'local'),
'status': 'healthy'
})
@app.route('/health')
def health():
return jsonify({'status': 'ok'})
@app.route('/api/process', methods=['POST'])
def process():
data = request.json
# Process data
result = transform_data(data)
return jsonify({'result': result})
def transform_data(data):
return {'processed': True, 'input': data}
if __name__ == '__main__':
port = int(os.environ.get('PORT', 8080))
app.run(host='0.0.0.0', port=port, debug=False)
Node.js Application
// server.js
import Fastify from 'fastify';
const fastify = Fastify({ logger: true });
fastify.get('/', async () => {
return {
service: 'my-api',
revision: process.env.K_REVISION || 'local',
status: 'healthy',
};
});
fastify.get('/health', async () => {
return { status: 'ok' };
});
fastify.post('/api/process', async (request) => {
const result = processData(request.body);
return { result };
});
function processData(data) {
return { processed: true, timestamp: new Date().toISOString() };
}
const start = async () => {
try {
const port = process.env.PORT || 8080;
await fastify.listen({ port, host: '0.0.0.0' });
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
start();
# Node.js Dockerfile
FROM node:20-slim as builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM node:20-slim
WORKDIR /app
RUN useradd --create-home appuser
COPY --from=builder /app/node_modules ./node_modules
COPY --chown=appuser:appuser . .
USER appuser
ENV PORT=8080
EXPOSE 8080
CMD ["node", "server.js"]
Deployment Methods
1. Direct Deployment
# Deploy from source
gcloud run deploy my-service \
--source . \
--region us-central1 \
--allow-unauthenticated
# Deploy from container
gcloud run deploy my-service \
--image us-central1-docker.pkg.dev/PROJECT_ID/my-repo/my-service:v1 \
--region us-central1 \
--platform managed
2. Cloud Build Pipeline
# cloudbuild.yaml
steps:
# Build the container image
- name: 'gcr.io/cloud-builders/docker'
args:
- 'build'
- '-t'
- 'us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/my-service:$COMMIT_SHA'
- '-t'
- 'us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/my-service:latest'
- '.'
# Push to Artifact Registry
- name: 'gcr.io/cloud-builders/docker'
args:
- 'push'
- '--all-tags'
- 'us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/my-service'
# Deploy to Cloud Run
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
entrypoint: gcloud
args:
- 'run'
- 'deploy'
- 'my-service'
- '--image'
- 'us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/my-service:$COMMIT_SHA'
- '--region'
- 'us-central1'
- '--platform'
- 'managed'
images:
- 'us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/my-service:$COMMIT_SHA'
- 'us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/my-service:latest'
3. Terraform Deployment
# main.tf
resource "google_cloud_run_v2_service" "main" {
name = "my-service"
location = var.region
template {
containers {
image = var.container_image
ports {
container_port = 8080
}
resources {
limits = {
cpu = "2"
memory = "1Gi"
}
cpu_idle = true # CPU throttling when idle
}
env {
name = "ENVIRONMENT"
value = var.environment
}
env {
name = "DATABASE_URL"
value_source {
secret_key_ref {
secret = google_secret_manager_secret.db_url.secret_id
version = "latest"
}
}
}
startup_probe {
http_get {
path = "/health"
port = 8080
}
initial_delay_seconds = 5
period_seconds = 10
failure_threshold = 3
}
liveness_probe {
http_get {
path = "/health"
port = 8080
}
period_seconds = 30
failure_threshold = 3
}
}
scaling {
min_instance_count = var.min_instances
max_instance_count = var.max_instances
}
service_account = google_service_account.cloud_run.email
}
traffic {
type = "TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST"
percent = 100
}
}
# Allow unauthenticated access (for public APIs)
resource "google_cloud_run_v2_service_iam_member" "public" {
count = var.allow_unauthenticated ? 1 : 0
location = google_cloud_run_v2_service.main.location
name = google_cloud_run_v2_service.main.name
role = "roles/run.invoker"
member = "allUsers"
}
# Custom domain mapping
resource "google_cloud_run_domain_mapping" "main" {
location = var.region
name = var.domain
metadata {
namespace = var.project_id
}
spec {
route_name = google_cloud_run_v2_service.main.name
}
}
Advanced Features
1. Traffic Splitting
# Deploy new revision with tag
gcloud run deploy my-service \
--image IMAGE:v2 \
--region us-central1 \
--no-traffic \
--tag canary
# Split traffic
gcloud run services update-traffic my-service \
--region us-central1 \
--to-tags canary=10
# Full rollout
gcloud run services update-traffic my-service \
--region us-central1 \
--to-latest
2. Cloud Run Jobs
# job-cloudbuild.yaml
steps:
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
entrypoint: gcloud
args:
- 'run'
- 'jobs'
- 'create'
- 'data-processor'
- '--image'
- 'us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/processor:latest'
- '--region'
- 'us-central1'
- '--tasks'
- '10'
- '--parallelism'
- '5'
- '--task-timeout'
- '3600s'
# Execute job
gcloud run jobs execute data-processor --region us-central1
# Schedule job with Cloud Scheduler
gcloud scheduler jobs create http daily-processor \
--schedule="0 2 * * *" \
--uri="https://us-central1-run.googleapis.com/apis/run.googleapis.com/v1/namespaces/PROJECT_ID/jobs/data-processor:run" \
--http-method=POST \
--oauth-service-account-email=scheduler-sa@PROJECT_ID.iam.gserviceaccount.com
3. VPC Connector
# Connect to VPC for private resources
resource "google_vpc_access_connector" "connector" {
name = "cloud-run-connector"
region = var.region
ip_cidr_range = "10.8.0.0/28"
network = google_compute_network.main.name
}
resource "google_cloud_run_v2_service" "main" {
# ...
template {
vpc_access {
connector = google_vpc_access_connector.connector.id
egress = "PRIVATE_RANGES_ONLY"
}
}
}
4. Cloud SQL Connection
# Using Cloud SQL connector
from google.cloud.sql.connector import Connector
import sqlalchemy
connector = Connector()
def get_connection():
conn = connector.connect(
"project:region:instance",
"pg8000",
user="user",
password=os.environ["DB_PASSWORD"],
db="database"
)
return conn
pool = sqlalchemy.create_engine(
"postgresql+pg8000://",
creator=get_connection,
)
5. Pub/Sub Push Subscription
# Cloud Run service triggered by Pub/Sub
PubSubService:
Type: gcloud.run.service
Properties:
# ...
template:
containers:
- image: IMAGE
env:
- name: PUBSUB_VERIFICATION_TOKEN
valueFrom:
secretKeyRef:
name: pubsub-token
@app.route('/pubsub', methods=['POST'])
def pubsub_handler():
envelope = request.get_json()
if not envelope:
return 'Bad Request: no message', 400
message = envelope.get('message', {})
data = base64.b64decode(message.get('data', '')).decode('utf-8')
# Process message
process_message(json.loads(data))
return '', 204
Security
1. Service Account
resource "google_service_account" "cloud_run" {
account_id = "cloud-run-sa"
display_name = "Cloud Run Service Account"
}
resource "google_project_iam_member" "cloud_run_sql" {
project = var.project_id
role = "roles/cloudsql.client"
member = "serviceAccount:${google_service_account.cloud_run.email}"
}
resource "google_secret_manager_secret_iam_member" "cloud_run_secrets" {
secret_id = google_secret_manager_secret.db_url.secret_id
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${google_service_account.cloud_run.email}"
}
2. IAM Authentication
# Calling authenticated Cloud Run service
import google.auth.transport.requests
import google.oauth2.id_token
def call_cloud_run_service(url, data):
auth_req = google.auth.transport.requests.Request()
id_token = google.oauth2.id_token.fetch_id_token(auth_req, url)
headers = {
'Authorization': f'Bearer {id_token}',
'Content-Type': 'application/json'
}
response = requests.post(url, headers=headers, json=data)
return response.json()
3. Binary Authorization
# Enable Binary Authorization
gcloud run services update my-service \
--binary-authorization=default \
--region us-central1
Monitoring
Cloud Logging
import google.cloud.logging
from google.cloud.logging_v2.handlers import CloudLoggingHandler
client = google.cloud.logging.Client()
handler = CloudLoggingHandler(client)
logger = logging.getLogger()
logger.addHandler(handler)
logger.setLevel(logging.INFO)
# Structured logging
logger.info('Request processed', extra={
'json_fields': {
'request_id': request_id,
'user_id': user_id,
'latency_ms': latency
}
})
Cloud Monitoring Alerts
resource "google_monitoring_alert_policy" "high_latency" {
display_name = "Cloud Run High Latency"
combiner = "OR"
conditions {
display_name = "Request latency > 5s"
condition_threshold {
filter = "resource.type = \"cloud_run_revision\" AND metric.type = \"run.googleapis.com/request_latencies\""
duration = "60s"
comparison = "COMPARISON_GT"
threshold_value = 5000
aggregations {
alignment_period = "60s"
per_series_aligner = "ALIGN_PERCENTILE_99"
}
}
}
notification_channels = [google_monitoring_notification_channel.email.name]
}
Performance Optimization
1. Instance Settings
gcloud run services update my-service \
--cpu 2 \
--memory 2Gi \
--concurrency 80 \
--min-instances 1 \
--max-instances 100 \
--cpu-boost \
--region us-central1
2. Startup Optimization
# Use multi-stage builds
# Pre-compile Python bytecode
RUN python -m compileall .
# Use slim images
FROM python:3.12-slim
3. Connection Pooling
from sqlalchemy.pool import QueuePool
engine = create_engine(
DATABASE_URL,
poolclass=QueuePool,
pool_size=5,
max_overflow=10,
pool_timeout=30,
pool_recycle=1800
)
Conclusion
Google Cloud Run provides an excellent platform for running containerized applications with minimal operational overhead. Its ability to scale to zero, combined with container flexibility, makes it ideal for APIs, microservices, and event-driven workloads.
Key takeaways:
- Use multi-stage Docker builds for smaller images
- Implement proper health checks
- Leverage Cloud Build for CI/CD
- Use VPC connectors for private resources
- Monitor with Cloud Logging and Monitoring
Resources
이 글이 도움이 되셨다면 공감 및 광고 클릭을 부탁드립니다 :)
