Skip to main content
โšก Calmops

Cloud Deployment: AWS, GCP, and Azure for Web Applications

Introduction

AWS, Google Cloud (GCP), and Azure each offer dozens of ways to deploy an application. This guide cuts through the noise and focuses on the most practical deployment options for web applications and APIs in 2026 โ€” with real CLI commands you can use today.

Prerequisites: Docker knowledge, basic CLI familiarity, an account on at least one cloud provider.

Choosing a Deployment Target

Your situation Recommended service
Containerized app, want simplicity Cloud Run (GCP) or App Runner (AWS)
Need auto-scaling, Kubernetes EKS (AWS), GKE (GCP), AKS (Azure)
Simple web app, no containers Elastic Beanstalk (AWS), App Engine (GCP), App Service (Azure)
Serverless functions Lambda (AWS), Cloud Functions (GCP), Azure Functions
Static site + API Amplify (AWS), Firebase Hosting (GCP), Static Web Apps (Azure)

Cloud Run is the simplest way to deploy a containerized app. It scales to zero (no cost when idle) and scales up automatically under load.

# Install gcloud CLI
# https://cloud.google.com/sdk/docs/install

# Authenticate
gcloud auth login
gcloud config set project YOUR_PROJECT_ID

# Deploy directly from source (builds and deploys in one step)
gcloud run deploy myapp \
    --source . \
    --platform managed \
    --region us-central1 \
    --allow-unauthenticated \
    --port 3000 \
    --memory 512Mi \
    --cpu 1 \
    --min-instances 0 \
    --max-instances 10

# Deploy from existing container image
gcloud run deploy myapp \
    --image gcr.io/YOUR_PROJECT/myapp:1.0 \
    --platform managed \
    --region us-central1

# Set environment variables
gcloud run services update myapp \
    --set-env-vars NODE_ENV=production,PORT=3000 \
    --region us-central1

# Set secrets (from Secret Manager)
gcloud run services update myapp \
    --set-secrets DATABASE_URL=database-url:latest \
    --region us-central1

# View logs
gcloud run services logs read myapp --region us-central1

# List services
gcloud run services list

# Get service URL
gcloud run services describe myapp --region us-central1 --format 'value(status.url)'

Cloud Run with Cloud SQL

# Create a Cloud SQL instance
gcloud sql instances create mydb \
    --database-version POSTGRES_16 \
    --tier db-f1-micro \
    --region us-central1

# Connect Cloud Run to Cloud SQL
gcloud run services update myapp \
    --add-cloudsql-instances YOUR_PROJECT:us-central1:mydb \
    --set-env-vars DB_HOST=/cloudsql/YOUR_PROJECT:us-central1:mydb \
    --region us-central1

AWS Elastic Container Service (ECS)

ECS runs Docker containers on AWS. Use Fargate to avoid managing EC2 instances:

# Install AWS CLI
# https://aws.amazon.com/cli/

# Configure credentials
aws configure

# Create ECR repository and push image
aws ecr create-repository --repository-name myapp --region us-east-1

# Get login token and push
aws ecr get-login-password --region us-east-1 | \
    docker login --username AWS --password-stdin \
    123456789.dkr.ecr.us-east-1.amazonaws.com

docker tag myapp:1.0 123456789.dkr.ecr.us-east-1.amazonaws.com/myapp:1.0
docker push 123456789.dkr.ecr.us-east-1.amazonaws.com/myapp:1.0
// task-definition.json
{
  "family": "myapp",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "256",
  "memory": "512",
  "executionRoleArn": "arn:aws:iam::123456789:role/ecsTaskExecutionRole",
  "containerDefinitions": [
    {
      "name": "myapp",
      "image": "123456789.dkr.ecr.us-east-1.amazonaws.com/myapp:1.0",
      "portMappings": [{"containerPort": 3000, "protocol": "tcp"}],
      "environment": [
        {"name": "NODE_ENV", "value": "production"}
      ],
      "secrets": [
        {"name": "DATABASE_URL", "valueFrom": "arn:aws:secretsmanager:us-east-1:123456789:secret:database-url"}
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/myapp",
          "awslogs-region": "us-east-1",
          "awslogs-stream-prefix": "ecs"
        }
      }
    }
  ]
}
# Register task definition
aws ecs register-task-definition --cli-input-json file://task-definition.json

# Create cluster
aws ecs create-cluster --cluster-name myapp-cluster

# Create service
aws ecs create-service \
    --cluster myapp-cluster \
    --service-name myapp \
    --task-definition myapp:1 \
    --desired-count 2 \
    --launch-type FARGATE \
    --network-configuration "awsvpcConfiguration={subnets=[subnet-xxx],securityGroups=[sg-xxx],assignPublicIp=ENABLED}"

# Update service (deploy new version)
aws ecs update-service \
    --cluster myapp-cluster \
    --service myapp \
    --task-definition myapp:2  # new task definition revision

# View service status
aws ecs describe-services --cluster myapp-cluster --services myapp

# View logs
aws logs tail /ecs/myapp --follow

AWS App Runner (Simpler than ECS)

App Runner is the simplest AWS container deployment โ€” similar to Cloud Run:

# Deploy from ECR image
aws apprunner create-service \
    --service-name myapp \
    --source-configuration '{
        "ImageRepository": {
            "ImageIdentifier": "123456789.dkr.ecr.us-east-1.amazonaws.com/myapp:1.0",
            "ImageRepositoryType": "ECR",
            "ImageConfiguration": {
                "Port": "3000",
                "RuntimeEnvironmentVariables": {
                    "NODE_ENV": "production"
                }
            }
        },
        "AutoDeploymentsEnabled": true
    }' \
    --instance-configuration '{"Cpu": "0.25 vCPU", "Memory": "0.5 GB"}'

# Get service URL
aws apprunner describe-service --service-arn arn:aws:apprunner:... \
    --query 'Service.ServiceUrl' --output text

Azure Container Apps

Azure Container Apps is Microsoft’s serverless container platform:

# Install Azure CLI
# https://docs.microsoft.com/en-us/cli/azure/install-azure-cli

# Login
az login

# Create resource group
az group create --name myapp-rg --location eastus

# Create Container Apps environment
az containerapp env create \
    --name myapp-env \
    --resource-group myapp-rg \
    --location eastus

# Deploy container
az containerapp create \
    --name myapp \
    --resource-group myapp-rg \
    --environment myapp-env \
    --image myregistry.azurecr.io/myapp:1.0 \
    --target-port 3000 \
    --ingress external \
    --min-replicas 0 \
    --max-replicas 10 \
    --cpu 0.5 \
    --memory 1.0Gi \
    --env-vars NODE_ENV=production

# Update with new image
az containerapp update \
    --name myapp \
    --resource-group myapp-rg \
    --image myregistry.azurecr.io/myapp:2.0

# View logs
az containerapp logs show --name myapp --resource-group myapp-rg --follow

# Get URL
az containerapp show --name myapp --resource-group myapp-rg \
    --query properties.configuration.ingress.fqdn --output tsv

Elastic Beanstalk (AWS, Non-Container)

For Node.js apps without Docker:

# Install EB CLI
pip install awsebcli

# Initialize
eb init -p node.js-20 myapp --region us-east-1

# Create environment
eb create production

# Deploy
eb deploy

# View logs
eb logs

# SSH into instance
eb ssh

# Terminate
eb terminate production

Secrets Management

Never put secrets in environment variables directly in deployment configs. Use managed secret stores:

# AWS Secrets Manager
aws secretsmanager create-secret \
    --name myapp/database-url \
    --secret-string "postgres://user:pass@host:5432/db"

# Reference in ECS task definition
"secrets": [{"name": "DATABASE_URL", "valueFrom": "arn:aws:secretsmanager:..."}]

# GCP Secret Manager
echo -n "postgres://user:pass@host:5432/db" | \
    gcloud secrets create database-url --data-file=-

# Reference in Cloud Run
gcloud run services update myapp \
    --set-secrets DATABASE_URL=database-url:latest

# Azure Key Vault
az keyvault secret set \
    --vault-name myapp-vault \
    --name database-url \
    --value "postgres://user:pass@host:5432/db"

CI/CD Integration

GitHub Actions โ†’ Cloud Run

# .github/workflows/deploy.yml
name: Deploy to Cloud Run

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: google-github-actions/auth@v2
        with:
          credentials_json: ${{ secrets.GCP_CREDENTIALS }}

      - uses: google-github-actions/deploy-cloudrun@v2
        with:
          service: myapp
          region: us-central1
          source: .

GitHub Actions โ†’ AWS ECS

name: Deploy to ECS

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: us-east-1

      - uses: aws-actions/amazon-ecr-login@v2

      - name: Build and push
        run: |
          docker build -t $ECR_REGISTRY/myapp:$GITHUB_SHA .
          docker push $ECR_REGISTRY/myapp:$GITHUB_SHA

      - uses: aws-actions/amazon-ecs-deploy-task-definition@v1
        with:
          task-definition: task-definition.json
          service: myapp
          cluster: myapp-cluster
          wait-for-service-stability: true

Cost Comparison (Approximate, 2026)

Service Free Tier ~$50/month gets you
Cloud Run (GCP) 2M requests/month ~10M requests + 5 vCPU-hours
App Runner (AWS) None 0.5 vCPU, 1GB RAM, always-on
Container Apps (Azure) 180,000 vCPU-s/month ~2M requests
ECS Fargate (AWS) None 0.25 vCPU, 0.5GB RAM, always-on

For low-traffic apps: Cloud Run and Container Apps are cheapest (scale to zero). For consistent traffic: ECS Fargate or App Runner may be more predictable.

Resources

Comments