# Space Duck — Production Deployment Checklist > Follow steps in order. Do not skip. Each step has a verification command. --- ## Prerequisites - AWS CLI v2 installed and configured - Python 3.9+ - AWS account with admin access (or scoped IAM user with required permissions) ```bash export AWS_ACCESS_KEY_ID=YOUR_KEY export AWS_SECRET_ACCESS_KEY=YOUR_SECRET export AWS_DEFAULT_REGION=us-east-1 ``` --- ## 1. IAM Setup Create IAM user `JP@DUCKGALAXY` with programmatic access. Attach these policies: - `AmazonDynamoDBFullAccess` - `AWSLambda_FullAccess` - `AmazonAPIGatewayAdministrator` - `AmazonCognitoPowerUser` - `AmazonS3FullAccess` - `CloudFrontFullAccess` - `AWSStepFunctionsFullAccess` - `AmazonSNSFullAccess` - `AmazonSESFullAccess` - `AWSCertificateManagerFullAccess` - `AmazonRoute53FullAccess` **Verify:** ```bash aws sts get-caller-identity ``` --- ## 2. DynamoDB Tables Create all 7 tables (on-demand billing): ```bash # eggs aws dynamodb create-table --table-name eggs \ --attribute-definitions AttributeName=egg_id,AttributeType=S \ --key-schema AttributeName=egg_id,KeyType=HASH \ --billing-mode PAY_PER_REQUEST # ducklings aws dynamodb create-table --table-name ducklings \ --attribute-definitions AttributeName=duckling_id,AttributeType=S \ --key-schema AttributeName=duckling_id,KeyType=HASH \ --billing-mode PAY_PER_REQUEST \ --stream-specification StreamEnabled=true,StreamViewType=NEW_AND_OLD_IMAGES # spaceducks aws dynamodb create-table --table-name spaceducks \ --attribute-definitions AttributeName=spaceduck_id,AttributeType=S \ --key-schema AttributeName=spaceduck_id,KeyType=HASH \ --billing-mode PAY_PER_REQUEST # birth_certificates aws dynamodb create-table --table-name birth_certificates \ --attribute-definitions AttributeName=cert_id,AttributeType=S \ --key-schema AttributeName=cert_id,KeyType=HASH \ --billing-mode PAY_PER_REQUEST # lobsters aws dynamodb create-table --table-name lobsters \ --attribute-definitions AttributeName=lobster_id,AttributeType=S \ --key-schema AttributeName=lobster_id,KeyType=HASH \ --billing-mode PAY_PER_REQUEST # connections (peck records) aws dynamodb create-table --table-name connections \ --attribute-definitions AttributeName=connection_id,AttributeType=S \ --key-schema AttributeName=connection_id,KeyType=HASH \ --billing-mode PAY_PER_REQUEST # audit_log aws dynamodb create-table --table-name audit_log \ --attribute-definitions AttributeName=log_id,AttributeType=S \ --key-schema AttributeName=log_id,KeyType=HASH \ --billing-mode PAY_PER_REQUEST ``` **Verify:** ```bash aws dynamodb list-tables --query 'TableNames' --output json # Expected: ["audit_log","birth_certificates","connections","ducklings","eggs","lobsters","spaceducks"] ``` --- ## 3. Cognito User Pool ```bash # Create user pool aws cognito-idp create-user-pool \ --pool-name spaceduckling-users \ --auto-verified-attributes email \ --username-attributes email \ --policies '{"PasswordPolicy":{"MinimumLength":8,"RequireUppercase":true,"RequireLowercase":true,"RequireNumbers":true,"RequireSymbols":false}}' \ --query 'UserPool.Id' --output text # Note the pool ID, then create app client aws cognito-idp create-user-pool-client \ --user-pool-id YOUR_POOL_ID \ --client-name spaceduckling-app \ --no-generate-secret \ --explicit-auth-flows ALLOW_USER_PASSWORD_AUTH ALLOW_REFRESH_TOKEN_AUTH \ --query 'UserPoolClient.ClientId' --output text ``` **Verify:** ```bash aws cognito-idp describe-user-pool --user-pool-id YOUR_POOL_ID --query 'UserPool.Status' ``` --- ## 4. Lambda IAM Role ```bash aws iam create-role \ --role-name mission-control-lambda-role \ --assume-role-policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"lambda.amazonaws.com"},"Action":"sts:AssumeRole"}]}' aws iam attach-role-policy --role-name mission-control-lambda-role \ --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole aws iam attach-role-policy --role-name mission-control-lambda-role \ --policy-arn arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess aws iam attach-role-policy --role-name mission-control-lambda-role \ --policy-arn arn:aws:iam::aws:policy/AmazonCognitoPowerUser aws iam attach-role-policy --role-name mission-control-lambda-role \ --policy-arn arn:aws:iam::aws:policy/AmazonSNSFullAccess aws iam attach-role-policy --role-name mission-control-lambda-role \ --policy-arn arn:aws:iam::aws:policy/AWSStepFunctionsFullAccess ``` --- ## 5. Lambda Function ```bash # Package python3 -c "import zipfile; z=zipfile.ZipFile('/tmp/lambda.zip','w'); z.write('lambda_v8.py','lambda_function.py'); z.close()" # Create function aws lambda create-function \ --function-name mission-control-api \ --runtime python3.12 \ --role arn:aws:iam::ACCOUNT_ID:role/mission-control-lambda-role \ --handler lambda_function.handler \ --zip-file fileb:///tmp/lambda.zip \ --timeout 30 \ --memory-size 256 \ --environment "Variables={ COGNITO_POOL_ID=YOUR_POOL_ID, COGNITO_CLIENT_ID=YOUR_CLIENT_ID, SFN_PECK_ARN=arn:aws:states:us-east-1:ACCOUNT_ID:stateMachine:peck-approval-workflow, TURNSTILE_SECRET=YOUR_TURNSTILE_SECRET, LAMBDA_VERSION=1 }" # Publish version and create aliases aws lambda publish-version --function-name mission-control-api --query Version --output text aws lambda create-alias --function-name mission-control-api --name prod --function-version 1 aws lambda create-alias --function-name mission-control-api --name staging --function-version 1 ``` **Verify:** ```bash aws lambda invoke --function-name mission-control-api:prod \ --payload '{"path":"/beak","httpMethod":"GET","headers":{},"body":null}' \ /tmp/test.json && cat /tmp/test.json # Expected: {"statusCode":200,"body":"{\"message\":\"Beak Gate live\"}"} ``` --- ## 6. Step Functions ```bash # Create IAM role for SFN aws iam create-role \ --role-name spaceduck-sfn-role \ --assume-role-policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"states.amazonaws.com"},"Action":"sts:AssumeRole"}]}' aws iam attach-role-policy --role-name spaceduck-sfn-role \ --policy-arn arn:aws:iam::aws:policy/AWSLambdaRole # Create state machine (see docs/reference/PECK-SFN-ARCHITECTURE.md for full definition) aws stepfunctions create-state-machine \ --name peck-approval-workflow \ --role-arn arn:aws:iam::ACCOUNT_ID:role/spaceduck-sfn-role \ --definition file://sfn-definition.json ``` --- ## 7. API Gateway ```bash # Create REST API aws apigateway create-rest-api --name mission-control-api --query 'id' --output text # Create prod stage deployment (see existing API Gateway setup for full resource/method tree) aws apigateway create-deployment --rest-api-id YOUR_API_ID --stage-name prod ``` --- ## 8. S3 Frontend Bucket ```bash BUCKET=mission-control-frontend-ACCOUNT_ID aws s3api create-bucket --bucket $BUCKET --region us-east-1 aws s3api put-public-access-block --bucket $BUCKET \ --public-access-block-configuration "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true" # Upload frontend files aws s3 sync ./frontend/ s3://$BUCKET/ --content-type text/html ``` --- ## 9. CloudFront Distribution ```bash # Create OAC aws cloudfront create-origin-access-control \ --origin-access-control-config \ "Name=SpaceDuckOAC,SigningProtocol=sigv4,SigningBehavior=always,OriginAccessControlOriginType=s3" # Create distribution (use console or CloudFormation for full config) # Key settings: # - Origin: S3 bucket with OAC # - Default root object: index.html # - Custom error: 404 → /404.html (200) # - Aliases: all 5 domains # - ACM cert: must cover all 5 domains (us-east-1 only) # - Price class: PriceClass_All ``` --- ## 10. Route 53 DNS For each domain, create: ```bash # A record (alias to CloudFront) aws route53 change-resource-record-sets \ --hosted-zone-id YOUR_ZONE_ID \ --change-batch '{ "Changes": [{ "Action": "CREATE", "ResourceRecordSet": { "Name": "yourdomain.com", "Type": "A", "AliasTarget": { "HostedZoneId": "Z2FDTNDATAQYW2", "DNSName": "YOUR_CF_DOMAIN.cloudfront.net", "EvaluateTargetHealth": false } } }] }' # ACM validation CNAMEs (get values from ACM console) ``` --- ## 11. ACM Certificates ```bash # Request cert (must be in us-east-1 for CloudFront) aws acm request-certificate \ --domain-name yourdomain.com \ --subject-alternative-names www.yourdomain.com \ --validation-method DNS \ --region us-east-1 # Add CNAME records from ACM to Route 53, then wait for ISSUED status aws acm wait certificate-validated \ --certificate-arn arn:aws:acm:us-east-1:ACCOUNT_ID:certificate/YOUR_CERT_ID ``` --- ## 12. Post-Deploy Smoke Tests ```bash API=https://YOUR_API_ID.execute-api.us-east-1.amazonaws.com/prod # Health check curl $API/beak # Metrics curl $API/beak/metrics # Frontend curl -I https://YOUR_CF_DOMAIN.cloudfront.net/index.html # Turnstile (should reject without token) curl -X POST $API/beak/signup \ -H "Content-Type: application/json" \ -d '{"email":"test@test.com","password":"Test1234!"}' | python3 -m json.tool # Expected: {"error": "Missing CAPTCHA token", "captcha_failed": true} ``` --- ## Current Production Values (reference) | Resource | ID | |----------|----| | AWS Account | 121546003735 | | Lambda | mission-control-api (prod=v30) | | API Gateway | czt9d57q83 | | CloudFront | E3HQHA5N284LTS | | Cognito Pool | us-east-1_OwEtInqCp | | S3 Bucket | mission-control-frontend-121546003735 | | SFN State Machine | peck-approval-workflow |