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) |
Google Cloud Run (Recommended for Containers)
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
- Google Cloud Run Documentation
- AWS ECS Documentation
- AWS App Runner Documentation
- Azure Container Apps Documentation
- Cloud Pricing Calculator
Comments