GCP Cloud Run: Complete Containerized Application Guide 2026


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


이 글이 도움이 되셨다면 공감 및 광고 클릭을 부탁드립니다 :)