diff --git a/docker/.env.aws.example b/docker/.env.aws.example new file mode 100644 index 00000000..af45ea7f --- /dev/null +++ b/docker/.env.aws.example @@ -0,0 +1,30 @@ +# AWS ECS Fargate deployment environment +# Copy to .env.aws and fill in values before deploying +# +# Secrets (DATABASE_URL, BETTER_AUTH_SECRET, ANTHROPIC_API_KEY, OPENAI_API_KEY, +# GITHUB_TOKEN) are injected via AWS Secrets Manager — do NOT set them here. + +# Deployment mode +PAPERCLIP_DEPLOYMENT_MODE=authenticated +PAPERCLIP_DEPLOYMENT_EXPOSURE=public +PAPERCLIP_PUBLIC_URL=https://paperclip.example.com + +# Server +HOST=0.0.0.0 +PORT=3100 +NODE_ENV=production +SERVE_UI=true + +# Paperclip paths +PAPERCLIP_HOME=/paperclip +PAPERCLIP_INSTANCE_ID=default +PAPERCLIP_CONFIG=/paperclip/instances/default/config.json + +# Auto-apply migrations on startup +PAPERCLIP_MIGRATION_AUTO_APPLY=true + +# Enable heartbeat scheduler for remote agents +HEARTBEAT_SCHEDULER_ENABLED=true + +# Post-deploy hardening (uncomment after first user signs up) +# PAPERCLIP_AUTH_DISABLE_SIGN_UP=true diff --git a/docker/ecs-task-definition.json b/docker/ecs-task-definition.json new file mode 100644 index 00000000..11a05378 --- /dev/null +++ b/docker/ecs-task-definition.json @@ -0,0 +1,90 @@ +{ + "family": "paperclip-server", + "networkMode": "awsvpc", + "requiresCompatibilities": ["FARGATE"], + "cpu": "2048", + "memory": "4096", + "executionRoleArn": "arn:aws:iam:::role/paperclip-ecs-execution", + "taskRoleArn": "arn:aws:iam:::role/paperclip-ecs-task", + "containerDefinitions": [ + { + "name": "paperclip-server", + "image": ".dkr.ecr..amazonaws.com/paperclip-server:latest", + "essential": true, + "portMappings": [ + { + "containerPort": 3100, + "protocol": "tcp" + } + ], + "environment": [ + { "name": "NODE_ENV", "value": "production" }, + { "name": "HOST", "value": "0.0.0.0" }, + { "name": "PORT", "value": "3100" }, + { "name": "SERVE_UI", "value": "true" }, + { "name": "PAPERCLIP_HOME", "value": "/paperclip" }, + { "name": "PAPERCLIP_INSTANCE_ID", "value": "default" }, + { "name": "PAPERCLIP_CONFIG", "value": "/paperclip/instances/default/config.json" }, + { "name": "PAPERCLIP_DEPLOYMENT_MODE", "value": "authenticated" }, + { "name": "PAPERCLIP_DEPLOYMENT_EXPOSURE", "value": "public" }, + { "name": "PAPERCLIP_PUBLIC_URL", "value": "https://" }, + { "name": "PAPERCLIP_MIGRATION_AUTO_APPLY", "value": "true" }, + { "name": "HEARTBEAT_SCHEDULER_ENABLED", "value": "true" } + ], + "secrets": [ + { + "name": "DATABASE_URL", + "valueFrom": "arn:aws:secretsmanager:::secret:paperclip/database-url" + }, + { + "name": "BETTER_AUTH_SECRET", + "valueFrom": "arn:aws:secretsmanager:::secret:paperclip/better-auth-secret" + }, + { + "name": "ANTHROPIC_API_KEY", + "valueFrom": "arn:aws:secretsmanager:::secret:paperclip/anthropic-api-key" + }, + { + "name": "OPENAI_API_KEY", + "valueFrom": "arn:aws:secretsmanager:::secret:paperclip/openai-api-key" + }, + { + "name": "GITHUB_TOKEN", + "valueFrom": "arn:aws:secretsmanager:::secret:paperclip/github-token" + } + ], + "mountPoints": [ + { + "sourceVolume": "paperclip-data", + "containerPath": "/paperclip", + "readOnly": false + } + ], + "healthCheck": { + "command": ["CMD-SHELL", "curl -f http://localhost:3100/api/health || exit 1"], + "interval": 30, + "timeout": 5, + "retries": 3, + "startPeriod": 60 + }, + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-group": "/ecs/paperclip", + "awslogs-region": "", + "awslogs-stream-prefix": "server" + } + } + } + ], + "volumes": [ + { + "name": "paperclip-data", + "efsVolumeConfiguration": { + "fileSystemId": "", + "rootDirectory": "/", + "transitEncryption": "ENABLED" + } + } + ] +} diff --git a/docs/deploy/aws-ecs.md b/docs/deploy/aws-ecs.md new file mode 100644 index 00000000..81e9baed --- /dev/null +++ b/docs/deploy/aws-ecs.md @@ -0,0 +1,580 @@ +--- +title: AWS ECS Fargate +summary: Deploy Paperclip to AWS using ECS Fargate, RDS Postgres, and EFS +--- + +Deploy Paperclip to AWS with ECS Fargate (compute), RDS Postgres 17 (database), and EFS (persistent storage). This guide uses the AWS CLI and produces a single-task ECS service behind an ALB with HTTPS. + +## Prerequisites + +- AWS CLI v2 configured with a profile that has admin-level permissions +- Docker installed locally (for building and pushing the image) +- A registered domain with DNS you control (for the TLS certificate) +- The Paperclip repo cloned locally + +Set these shell variables for the rest of the guide: + +```bash +export AWS_REGION=us-east-1 +export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) +export PAPERCLIP_DOMAIN=paperclip.example.com # your domain +export DB_PASSWORD=$(openssl rand -base64 24 | tr -d '/+=' | head -c 32) +export AUTH_SECRET=$(openssl rand -base64 32) +``` + +## 1. Create ECR Repository + +```bash +aws ecr create-repository \ + --repository-name paperclip-server \ + --image-scanning-configuration scanOnPush=true \ + --region $AWS_REGION +``` + +## 2. Build and Push Docker Image + +```bash +cd /path/to/paperclip + +# Authenticate Docker to ECR +aws ecr get-login-password --region $AWS_REGION \ + | docker login --username AWS --password-stdin \ + $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com + +# Build +docker build -t paperclip-server . + +# Tag and push +docker tag paperclip-server:latest \ + $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/paperclip-server:latest + +docker push \ + $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/paperclip-server:latest +``` + +## 3. Networking (VPC, Subnets, Security Groups) + +Use the default VPC or create a dedicated one. The guide assumes the default VPC with public and private subnets in two AZs. + +```bash +# Get default VPC +VPC_ID=$(aws ec2 describe-vpcs \ + --filters Name=isDefault,Values=true \ + --query 'Vpcs[0].VpcId' --output text) + +# Get two public subnets (for ALB) +SUBNET_IDS=$(aws ec2 describe-subnets \ + --filters Name=vpc-id,Values=$VPC_ID \ + --query 'Subnets[?MapPublicIpOnLaunch==`true`] | [0:2].SubnetId' \ + --output text) +SUBNET_1=$(echo $SUBNET_IDS | awk '{print $1}') +SUBNET_2=$(echo $SUBNET_IDS | awk '{print $2}') +``` + +Create security groups: + +```bash +# ALB security group — inbound HTTPS +ALB_SG=$(aws ec2 create-security-group \ + --group-name paperclip-alb \ + --description "Paperclip ALB" \ + --vpc-id $VPC_ID \ + --query 'GroupId' --output text) + +aws ec2 authorize-security-group-ingress \ + --group-id $ALB_SG \ + --protocol tcp --port 443 --cidr 0.0.0.0/0 + +# Also open port 80 so the ALB can accept HTTP and redirect to HTTPS +aws ec2 authorize-security-group-ingress \ + --group-id $ALB_SG \ + --protocol tcp --port 80 --cidr 0.0.0.0/0 + +# ECS task security group — inbound from ALB only +ECS_SG=$(aws ec2 create-security-group \ + --group-name paperclip-ecs \ + --description "Paperclip ECS tasks" \ + --vpc-id $VPC_ID \ + --query 'GroupId' --output text) + +aws ec2 authorize-security-group-ingress \ + --group-id $ECS_SG \ + --protocol tcp --port 3100 \ + --source-group $ALB_SG + +# RDS security group — inbound from ECS only +RDS_SG=$(aws ec2 create-security-group \ + --group-name paperclip-rds \ + --description "Paperclip RDS" \ + --vpc-id $VPC_ID \ + --query 'GroupId' --output text) + +aws ec2 authorize-security-group-ingress \ + --group-id $RDS_SG \ + --protocol tcp --port 5432 \ + --source-group $ECS_SG + +# EFS security group — inbound NFS from ECS only +EFS_SG=$(aws ec2 create-security-group \ + --group-name paperclip-efs \ + --description "Paperclip EFS" \ + --vpc-id $VPC_ID \ + --query 'GroupId' --output text) + +aws ec2 authorize-security-group-ingress \ + --group-id $EFS_SG \ + --protocol tcp --port 2049 \ + --source-group $ECS_SG +``` + +## 4. Create RDS Postgres Instance + +```bash +# Custom VPCs don't come with a default DB subnet group — create one +# that spans our two subnets so RDS can place the instance. +aws rds create-db-subnet-group \ + --db-subnet-group-name paperclip-db-subnet \ + --db-subnet-group-description "Paperclip RDS subnets" \ + --subnet-ids $SUBNET_1 $SUBNET_2 + +aws rds create-db-instance \ + --db-instance-identifier paperclip-db \ + --db-instance-class db.t4g.micro \ + --engine postgres \ + --engine-version 17 \ + --master-username paperclip \ + --master-user-password "$DB_PASSWORD" \ + --allocated-storage 20 \ + --storage-type gp3 \ + --vpc-security-group-ids $RDS_SG \ + --db-subnet-group-name paperclip-db-subnet \ + --no-publicly-accessible \ + --backup-retention-period 7 \ + --no-multi-az \ + --db-name paperclip \ + --region $AWS_REGION + +# Wait for it to become available (takes 5-10 min) +aws rds wait db-instance-available \ + --db-instance-identifier paperclip-db + +# Get the endpoint +RDS_ENDPOINT=$(aws rds describe-db-instances \ + --db-instance-identifier paperclip-db \ + --query 'DBInstances[0].Endpoint.Address' --output text) + +DATABASE_URL="postgresql://paperclip:${DB_PASSWORD}@${RDS_ENDPOINT}:5432/paperclip" +``` + +## 5. Create EFS Filesystem + +```bash +EFS_ID=$(aws efs create-file-system \ + --performance-mode generalPurpose \ + --throughput-mode bursting \ + --encrypted \ + --tags Key=Name,Value=paperclip-data \ + --query 'FileSystemId' --output text) + +# Create mount targets in each subnet +for SUBNET in $SUBNET_1 $SUBNET_2; do + aws efs create-mount-target \ + --file-system-id $EFS_ID \ + --subnet-id $SUBNET \ + --security-groups $EFS_SG +done + +# Wait for mount targets +aws efs describe-mount-targets --file-system-id $EFS_ID +``` + +## 6. Store Secrets + +```bash +aws secretsmanager create-secret \ + --name paperclip/database-url \ + --secret-string "$DATABASE_URL" + +aws secretsmanager create-secret \ + --name paperclip/anthropic-api-key \ + --secret-string "YOUR_ANTHROPIC_KEY" + +aws secretsmanager create-secret \ + --name paperclip/better-auth-secret \ + --secret-string "$AUTH_SECRET" + +aws secretsmanager create-secret \ + --name paperclip/openai-api-key \ + --secret-string "YOUR_OPENAI_KEY" + +aws secretsmanager create-secret \ + --name paperclip/github-token \ + --secret-string "YOUR_GITHUB_PAT" +``` + +## 7. IAM Roles + +Create the ECS task execution role (pulls images, reads secrets) and the task role (application permissions). + +```bash +# Task execution role +aws iam create-role \ + --role-name paperclip-ecs-execution \ + --assume-role-policy-document '{ + "Version": "2012-10-17", + "Statement": [{ + "Effect": "Allow", + "Principal": {"Service": "ecs-tasks.amazonaws.com"}, + "Action": "sts:AssumeRole" + }] + }' + +aws iam attach-role-policy \ + --role-name paperclip-ecs-execution \ + --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy + +# Allow reading secrets +aws iam put-role-policy \ + --role-name paperclip-ecs-execution \ + --policy-name SecretsAccess \ + --policy-document '{ + "Version": "2012-10-17", + "Statement": [{ + "Effect": "Allow", + "Action": ["secretsmanager:GetSecretValue"], + "Resource": "arn:aws:secretsmanager:'$AWS_REGION':'$AWS_ACCOUNT_ID':secret:paperclip/*" + }] + }' + +# Task role (application — add permissions as needed) +aws iam create-role \ + --role-name paperclip-ecs-task \ + --assume-role-policy-document '{ + "Version": "2012-10-17", + "Statement": [{ + "Effect": "Allow", + "Principal": {"Service": "ecs-tasks.amazonaws.com"}, + "Action": "sts:AssumeRole" + }] + }' +``` + +## 8. ECS Cluster and Task Definition + +```bash +aws ecs create-cluster --cluster-name paperclip + +aws logs create-log-group --log-group-name /ecs/paperclip +``` + +Register the task definition using the template at `docker/ecs-task-definition.json`. Before registering, replace the placeholder values: + +```bash +sed -e "s||$AWS_ACCOUNT_ID|g" \ + -e "s||$AWS_REGION|g" \ + -e "s||$EFS_ID|g" \ + -e "s||$PAPERCLIP_DOMAIN|g" \ + docker/ecs-task-definition.json > /tmp/paperclip-task-def.json + +aws ecs register-task-definition \ + --cli-input-json file:///tmp/paperclip-task-def.json +``` + +## 9. ALB and TLS Certificate + +Request a certificate (you must validate via DNS): + +```bash +CERT_ARN=$(aws acm request-certificate \ + --domain-name $PAPERCLIP_DOMAIN \ + --validation-method DNS \ + --query 'CertificateArn' --output text) + +# Get the CNAME record to add to your DNS +aws acm describe-certificate \ + --certificate-arn $CERT_ARN \ + --query 'Certificate.DomainValidationOptions[0].ResourceRecord' +``` + +Add the CNAME to your DNS provider, then wait for validation: + +```bash +aws acm wait certificate-validated --certificate-arn $CERT_ARN +``` + +Create the ALB: + +```bash +ALB_ARN=$(aws elbv2 create-load-balancer \ + --name paperclip-alb \ + --subnets $SUBNET_1 $SUBNET_2 \ + --security-groups $ALB_SG \ + --scheme internet-facing \ + --type application \ + --query 'LoadBalancers[0].LoadBalancerArn' --output text) + +ALB_DNS=$(aws elbv2 describe-load-balancers \ + --load-balancer-arns $ALB_ARN \ + --query 'LoadBalancers[0].DNSName' --output text) + +# Target group +TG_ARN=$(aws elbv2 create-target-group \ + --name paperclip-tg \ + --protocol HTTP \ + --port 3100 \ + --vpc-id $VPC_ID \ + --target-type ip \ + --health-check-path /api/health \ + --health-check-interval-seconds 30 \ + --healthy-threshold-count 2 \ + --unhealthy-threshold-count 3 \ + --query 'TargetGroups[0].TargetGroupArn' --output text) + +# HTTPS listener +LISTENER_ARN=$(aws elbv2 create-listener \ + --load-balancer-arn $ALB_ARN \ + --protocol HTTPS \ + --port 443 \ + --certificates CertificateArn=$CERT_ARN \ + --default-actions Type=forward,TargetGroupArn=$TG_ARN \ + --query 'Listeners[0].ListenerArn' --output text) + +# HTTP listener — redirect all :80 traffic to :443 +HTTP_LISTENER_ARN=$(aws elbv2 create-listener \ + --load-balancer-arn $ALB_ARN \ + --protocol HTTP \ + --port 80 \ + --default-actions Type=redirect,RedirectConfig='{Protocol=HTTPS,Port=443,StatusCode=HTTP_301}' \ + --query 'Listeners[0].ListenerArn' --output text) +``` + +Point your DNS to the ALB: +- Create a CNAME or ALIAS record for `$PAPERCLIP_DOMAIN` -> `$ALB_DNS` + +## 10. Create ECS Service + +```bash +aws ecs create-service \ + --cluster paperclip \ + --service-name paperclip-server \ + --task-definition paperclip-server \ + --desired-count 1 \ + --launch-type FARGATE \ + --deployment-configuration '{ + "deploymentCircuitBreaker": {"enable": true, "rollback": true}, + "maximumPercent": 200, + "minimumHealthyPercent": 100 + }' \ + --network-configuration '{ + "awsvpcConfiguration": { + "subnets": ["'$SUBNET_1'", "'$SUBNET_2'"], + "securityGroups": ["'$ECS_SG'"], + "assignPublicIp": "ENABLED" + } + }' \ + --load-balancers '[{ + "targetGroupArn": "'$TG_ARN'", + "containerName": "paperclip-server", + "containerPort": 3100 + }]' +``` + +> **Note:** `assignPublicIp: ENABLED` is needed if using public subnets without a NAT Gateway. For private subnets, set to `DISABLED` and ensure a NAT Gateway is configured for outbound internet access. + +## 11. Verify Deployment + +```bash +# Watch task come up +aws ecs describe-services \ + --cluster paperclip \ + --services paperclip-server \ + --query 'services[0].{desired:desiredCount,running:runningCount,status:status}' + +# Check task health +aws ecs list-tasks --cluster paperclip --service-name paperclip-server +TASK_ARN=$(aws ecs list-tasks --cluster paperclip --service-name paperclip-server --query 'taskArns[0]' --output text) +aws ecs describe-tasks --cluster paperclip --tasks $TASK_ARN \ + --query 'tasks[0].{status:lastStatus,health:healthStatus}' + +# Check logs +aws logs tail /ecs/paperclip --since 10m --follow + +# Hit the health endpoint +curl -sf https://$PAPERCLIP_DOMAIN/api/health +``` + +**Healthy indicators:** +- ECS task status: `RUNNING`, health: `HEALTHY` +- Logs show `plugin job coordinator started` and `plugin-loader: loadAll complete` +- `/api/health` returns 200 + +## Post-Deploy Security Hardening + +After the first user has signed up (which grants admin role), lock down the instance: + +```bash +# Disable public sign-up (prevents unauthorized users from creating accounts) +# Add to the task definition environment section, then redeploy: +# { "name": "PAPERCLIP_AUTH_DISABLE_SIGN_UP", "value": "true" } + +# Or update via Secrets Manager / task def override, then force new deployment +aws ecs update-service \ + --cluster paperclip \ + --service paperclip-server \ + --force-new-deployment +``` + +Use the invite flow (added in v2026.416.0) to grant access to additional users after sign-up is disabled. + +## Deploying Updates + +Build, push, and force a new deployment: + +```bash +# Build and push new image +docker build -t paperclip-server . +docker tag paperclip-server:latest \ + $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/paperclip-server:latest +docker push \ + $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/paperclip-server:latest + +# Roll out +aws ecs update-service \ + --cluster paperclip \ + --service paperclip-server \ + --force-new-deployment + +# Watch the deployment +aws ecs describe-services \ + --cluster paperclip \ + --services paperclip-server \ + --query 'services[0].deployments[*].{status:status,running:runningCount,desired:desiredCount,rollout:rolloutState}' +``` + +ECS performs a rolling update: starts a new task, waits for it to pass health checks, then drains the old task. + +## Rollback + +If the new deployment is unhealthy: + +```bash +# ECS automatically rolls back if the new task fails health checks +# (circuit breaker is enabled in the service configuration above). +# To force rollback manually: + +# 1. Find the previous task definition revision +aws ecs list-task-definitions \ + --family-prefix paperclip-server \ + --sort DESC \ + --query 'taskDefinitionArns[0:3]' + +# 2. Update service to the previous revision +aws ecs update-service \ + --cluster paperclip \ + --service paperclip-server \ + --task-definition paperclip-server: +``` + +## Scaling to Zero (Cost Savings) + +Scale down when not in use: + +```bash +# Stop +aws ecs update-service \ + --cluster paperclip \ + --service paperclip-server \ + --desired-count 0 + +# Start +aws ecs update-service \ + --cluster paperclip \ + --service paperclip-server \ + --desired-count 1 +``` + +RDS can also be stopped (auto-restarts after 7 days): + +```bash +aws rds stop-db-instance --db-instance-identifier paperclip-db +aws rds start-db-instance --db-instance-identifier paperclip-db +``` + +## Teardown + +Remove all resources in reverse order: + +```bash +# 1. ECS service and cluster +aws ecs update-service --cluster paperclip --service paperclip-server --desired-count 0 +aws ecs delete-service --cluster paperclip --service paperclip-server --force +aws ecs delete-cluster --cluster paperclip + +# 2. ALB and ACM cert +aws elbv2 delete-listener --listener-arn $HTTP_LISTENER_ARN +aws elbv2 delete-listener --listener-arn $LISTENER_ARN +aws elbv2 delete-target-group --target-group-arn $TG_ARN +aws elbv2 delete-load-balancer --load-balancer-arn $ALB_ARN +aws acm delete-certificate --certificate-arn $CERT_ARN + +# 3. RDS (creates final snapshot) +aws rds delete-db-instance \ + --db-instance-identifier paperclip-db \ + --final-db-snapshot-identifier paperclip-db-final +aws rds wait db-instance-deleted --db-instance-identifier paperclip-db +aws rds delete-db-subnet-group --db-subnet-group-name paperclip-db-subnet + +# 4. EFS (mount targets must be deleted first) +for MT in $(aws efs describe-mount-targets --file-system-id $EFS_ID --query 'MountTargets[*].MountTargetId' --output text); do + aws efs delete-mount-target --mount-target-id $MT +done +# Mount-target deletion is async; poll until none remain before deleting +# the filesystem, otherwise delete-file-system fails with FileSystemInUse. +echo "Waiting for mount targets to delete..." +while aws efs describe-mount-targets \ + --file-system-id $EFS_ID \ + --query 'MountTargets[0].MountTargetId' --output text 2>/dev/null | grep -q 'fsmt-'; do + sleep 5 +done +aws efs delete-file-system --file-system-id $EFS_ID + +# 5. Secrets +for s in database-url anthropic-api-key better-auth-secret openai-api-key github-token; do + aws secretsmanager delete-secret --secret-id paperclip/$s --force-delete-without-recovery +done + +# 6. Security groups (after all dependents are gone) +for sg in $EFS_SG $RDS_SG $ECS_SG $ALB_SG; do + aws ec2 delete-security-group --group-id $sg +done + +# 7. ECR +aws ecr delete-repository --repository-name paperclip-server --force + +# 8. IAM roles +aws iam delete-role-policy --role-name paperclip-ecs-execution --policy-name SecretsAccess +aws iam detach-role-policy --role-name paperclip-ecs-execution \ + --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy +aws iam delete-role --role-name paperclip-ecs-execution +aws iam delete-role --role-name paperclip-ecs-task + +# 9. Log group +aws logs delete-log-group --log-group-name /ecs/paperclip +``` + +## Cost Reference + +| Service | Config | Monthly | +|---------|--------|---------| +| ECS Fargate | 2 vCPU, 4 GB, 24/7 | ~$70 | +| RDS Postgres | db.t4g.micro, 20 GB | ~$15 | +| ALB | 1 LCU average | ~$22 | +| NAT Gateway | 1 AZ (if using private subnets) | ~$35 | +| EFS | 1 GB Standard | ~$0.30 | +| Secrets Manager | 5 secrets | ~$2 | +| CloudWatch Logs | ~1 GB/mo | ~$0.50 | +| ECR | ~1 GB | ~$0.10 | +| **Total (public subnets, no NAT)** | | **~$110/mo** | +| **Total (private subnets + NAT)** | | **~$145/mo** | + +Use Fargate Spot and scheduled scaling to 0 during off-hours to reduce to ~$60-85/mo. diff --git a/docs/deploy/overview.md b/docs/deploy/overview.md index 3820642f..903eaec2 100644 --- a/docs/deploy/overview.md +++ b/docs/deploy/overview.md @@ -40,7 +40,7 @@ Paperclip supports three deployment configurations, from zero-friction local to - **Just trying Paperclip?** Use `local_trusted` (the default) - **Sharing with a team on private network?** Use `authenticated` + `private` -- **Deploying to the cloud?** Use `authenticated` + `public` +- **Deploying to the cloud?** Use `authenticated` + `public` — see [AWS ECS Fargate guide](aws-ecs.md) Set the mode during onboarding: