πŸ”

Redeploy Your App After Your Sandbox Expires

⏱ ~20–30 min

Your sandbox lease ended, or your team got a new sandbox account, and your app stopped working. Nothing is wrong with your code. This guide gets the same app running again in the new account.

Your app is just code. The code lives on GitHub and was never deleted, so a redeploy is a copy-and-run exercise. Most teams are live again in about 20 minutes.

What Happens When a Sandbox Lease Ends

When a lease ends, the sandbox account is wiped clean and returned to the pool. A new lease gives you an AWS account that is almost always a different one (a new Account ID), with nothing in it.

ItemAfter expiryWhat to do
Your code and workflow files on GitHubβœ… KeptNothing
Your API key value, if your app uses one (the string you chose in Step 6)βœ… Kept (it's just a string)Reuse the same value
Your GitHub secrets⚠️ Kept, but the AWS keys inside no longer workKiro does not need them. Replace them only for the optional GitHub Actions deploy
AWS Account IDπŸ”„ New with a new leaseStep 1
Lambda, API Gateway, S3 buckets, CloudFormation stack❌ DeletedRedeploy (Step 3)
IAM user github-deployer and its access keys❌ DeletedOnly needed for the optional GitHub Actions deploy
DynamoDB tables, uploaded files, CloudWatch logs❌ Deleted for goodRe-create and re-seed (Step 5)
CloudFront, certificate, WAF, custom domain❌ DeletedOptional (Step 5)
Your API URL and website URLπŸ”„ New after redeployStep 4

Before You Start

Step 1 β€” Get Into Your New Account

  1. Open aws-sandbox.eliteacademy.id β†’ Leases β†’ your new lease β†’ Access Account.
    Or go straight to the sign-in portal eliteacademy.awsapps.com/start, enter your email, and choose the account named like ETA-Sandbox-NN β†’ Management console.
  2. Write down your Account ID (top-right corner, click your name). It is different from your old account.
  3. Choose Asia Pacific (Malaysia) in the region menu (top-right). Sandbox accounts only allow these five regions:
RegionCode
Malaysia (recommended, used in this tutorial)ap-southeast-5
Singaporeap-southeast-1
Jakartaap-southeast-3
N. Virginiaus-east-1
Oregonus-west-2
Use the Account ID of your current lease, every time. A new lease usually gives you a different account. An old account that was returned to the pool is blocked by design: every call fails with "explicit deny in a service control policy … p-1lb4bh9n", and nothing an administrator changes will fix it. Check the Account ID shown on your Active lease, and make sure your computer's sandbox profile uses that number.
Any other region fails with "explicit deny in a service control policy". This is a sandbox rule, not a bug in your app.
Your console session lasts 1 hour. If the page asks you to sign in again, reopen it from the sign-in portal. Nothing is lost. If the Innovation Sandbox web app says "Session expired", click Login Again, or use the sign-in portal directly.

Step 2 β€” Check That Bedrock Works

  1. Search for Bedrock in the console, make sure you are in your region, then open Playground β†’ Chat.
  2. Pick a model, type hi, and click Run. You should get an answer.

In current sandbox accounts the Anthropic models work without a separate Model access request. If Bedrock shows a form asking about your use case, fill it in (for example "Student hackathon project") and wait for approval, which is usually instant.

Model IDs That Work in a Sandbox

Checked in a sandbox account, region Malaysia, in October 2026. Use these exact IDs in your Lambda code.

ModelID to use in codeNotes
Claude Haiku 4.5 (recommended)global.anthropic.claude-haiku-4-5-20251001-v1:0Fast and cheap. Works in Malaysia and the other four regions. A brand-new account needs the one-time Anthropic form first.
Claude Haiku 5.5global.anthropic.claude-haiku-5-5Newer, but AWS enables it per account: it may answer "not available for this account"
Claude Sonnet 5.5global.anthropic.claude-sonnet-5-5More capable, costs more per request
Claude Sonnet 5global.anthropic.claude-sonnet-5Previous Sonnet generation
Claude Sonnet 4.6 / 4.5global.anthropic.claude-sonnet-4-6
global.anthropic.claude-sonnet-4-5-20250929-v1:0
Older Sonnet versions
Amazon Nova 2 Liteglobal.amazon.nova-2-lite-v1:0Low-cost alternative
Claude 3 Haiku, Llama 3, image-generation modelsβ€”Not offered in Malaysia
Claude Opus (any version)β€”βŒ Blocked in sandbox accounts
Use the global. version of the ID. The plain ID anthropic.claude-haiku-4-5-20251001-v1:0 fails with "on-demand throughput isn’t supported". If your old code used the plain ID, change it in your Lambda file and in template.yaml before you redeploy.
Image-generation models are not available in Malaysia. If your app has an image widget, ask Kiro to call the image model in us-west-2 (Oregon, also allowed in the sandbox) and test it in the Playground first.

If your Lambda gets an AccessDenied that does not mention a service control policy, your function's role is missing Bedrock permissions. Make sure its policy in template.yaml includes:

- Effect: Allow
  Action:
    - bedrock:InvokeModel
    - bedrock:InvokeModelWithResponseStream
  Resource:
    - !Sub arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:inference-profile/global.*
    - !Sub arn:aws:bedrock:${AWS::Region}::foundation-model/*
    - arn:aws:bedrock:::foundation-model/*

Step 3 β€” Let Kiro Deploy Everything

You do not run any command yourself. Copy one prompt into Kiro and Kiro does the whole job on your computer: installs the tools that are missing (AWS CLI, SAM CLI, uv), sets up your sandbox sign-in, fixes the region and models, deploys, uploads the website and tests it. The only thing you do by hand is click Allow when the browser sign-in page opens, and answer if Kiro asks for your new Account ID.

  1. Open Kiro and open your project folder (File β†’ Open Folder). Not installed yet? Follow Kiro Login via Elite Academy Portal first.
  2. Keep Kiro in Supervised mode, so it shows each command and waits for your Approve click.
  3. Have your new Account ID ready (from Step 1). Paste the prompt below into the Kiro chat and send it.
You are my deployment assistant. My AWS sandbox expired and I got a NEW sandbox account. Redeploy this project there. You do EVERYTHING yourself: install missing tools, set up my sign-in, run every command, read the output, fix problems, and report. I only click "Allow" in the browser when the sign-in page opens. Never ask me to run commands.

RULES (follow strictly)
- Only touch the account that the AWS profile "sandbox" points to. Use --profile sandbox --region ap-southeast-5 (Malaysia) on every aws and sam command.
- Never create IAM users or access keys, never print or ask for secrets, never delete anything outside this project's own stack, never run git push, never overwrite an existing AWS profile or config file without making a backup copy first.
- Before each step, say in one line what you are about to do. If something fails, read the error, find the root cause (CloudFormation events, Lambda logs), fix it and retry. Stop and ask me only if you need a decision, my Account ID, or my sign-in.

STEPS
0. SET UP TOOLS. Detect my operating system. Check aws --version (need AWS CLI v2), sam --version (AWS SAM CLI) and uv --version. Install whatever is missing with the official method for my OS (macOS: Homebrew or the official installer; Windows: winget or the official MSI; Linux: the official installer). Ask me before anything that needs administrator or sudo rights. Show the final versions.
1. SET UP MY SIGN-IN. FIRST ask me for my NEW sandbox Account ID (12 digits, from my Active lease in the Innovation Sandbox web app) if I did not write it above, and remember it. Then run "aws sts get-caller-identity --profile sandbox". If it works AND the Account number equals the one I gave, skip to step 2. If the profile exists but shows a DIFFERENT account, it is an old or expired sandbox: do NOT use it, tell me, and fix the profile as below. If any later call fails with "explicit deny in a service control policy" (for example p-1lb4bh9n), do NOT ask me to contact an administrator: first check that the Account ID is my current active lease, because an old account that was returned to the pool is blocked on purpose. Back up ~/.aws/config, then add these blocks (if a profile named sandbox exists, show me and ask first):
[sso-session sandbox]
sso_start_url = https://eliteacademy.awsapps.com/start
sso_region = ap-southeast-1
sso_registration_scopes = sso:account:access
[profile sandbox]
sso_session = sandbox
sso_account_id = <MY ACCOUNT ID>
sso_role_name = myisb_IsbUsersPS
region = ap-southeast-5
output = json
Then run: aws sso login --profile sandbox . A browser opens: tell me to click Allow, then wait. Finally run aws sts get-caller-identity --profile sandbox and show the Account ID. It must equal the one I gave. If the session expires later (ExpiredToken), run the login again and continue.
2. CONNECT THE AWS MCP SERVER (optional helper). Create or merge .kiro/settings/mcp.json in this project with a server named aws-mcp-sandbox: command uvx, timeout 100000, transport stdio, args ["mcp-proxy-for-aws@latest", "https://aws-mcp.us-east-1.api.aws/mcp", "--profile", "sandbox", "--metadata", "AWS_REGION=ap-southeast-5"]. Keep any other servers already in the file. If the server does not show up as connected, do not wait: continue with the normal aws and sam commands, and tell me I can reload MCP servers later.
3. READ THE PROJECT. Read .github/workflows/deploy.yml and infra/template.yaml. Report the stack name, the template path, and every placeholder like __URL_*__ in frontend/index.html.
4. FIX OLD REGION AND MODELS. Search the whole project for "ap-southeast-1" and other hard-coded regions, and for old model IDs. Change them to ap-southeast-5 in infra/template.yaml and .github/workflows/deploy.yml (also the Bedrock IAM ARNs). Use only these model IDs: global.anthropic.claude-haiku-4-5-20251001-v1:0 (recommended), global.anthropic.claude-haiku-5-5 (only if it works), global.anthropic.claude-sonnet-5-5, global.amazon.nova-2-lite-v1:0. For each model the IAM policy needs three ARNs: arn:aws:bedrock:ap-southeast-5:<ACCOUNT_ID>:inference-profile/<id>, arn:aws:bedrock:ap-southeast-5::foundation-model/<base id without global.>, and arn:aws:bedrock:::foundation-model/<base id without global.>. Show me a short summary of what you changed.
5. CHECK THE MODELS. With aws bedrock-runtime converse in ap-southeast-5 (maxTokens 20, message "hi"), test these in order and show each result: global.anthropic.claude-haiku-4-5-20251001-v1:0, global.anthropic.claude-haiku-5-5, global.anthropic.claude-sonnet-5-5, global.amazon.nova-2-lite-v1:0. Use the FIRST one that answers as the app's default model: set it wherever the project defines its model ID (code, template environment variable, or config), so the app works today. Do not stop to ask me about this. If a model fails, quote the exact error and use this table: "Model use case details have not been submitted" = this account has not filled the Anthropic use-case form yet (tell me; I can fill it later in the Bedrock console, or you can submit it with aws bedrock put-use-case-for-model-access only after I give you the five answers: company name, website, intended users, industry, use case); "explicit deny" = the sandbox blocks that model, do not try to bypass it; "model identifier is invalid" = wrong model ID or not offered in Malaysia.
6. BUILD AND DEPLOY. Run sam build --template infra/template.yaml, then sam deploy --stack-name <stack from deploy.yml> --resolve-s3 --capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM --region ap-southeast-5 --profile sandbox --no-confirm-changeset --no-fail-on-empty-changeset. Do NOT use the GitHub secrets or --s3-bucket. IMPORTANT: Lambda Function URLs (FunctionUrlConfig) do not exist in ap-southeast-5. If the template uses them, replace them with an AWS::Serverless::HttpApi (CORS *) and an HttpApi event per function, keep the same URL placeholders, and tell me what you changed.
7. WEBSITE. Read the stack outputs. Replace every __URL_*__ placeholder in frontend/index.html with the matching output URL (same logic as the workflow). If the file contains old real URLs or an old Account ID instead of placeholders, replace those too. Then upload with aws s3 sync frontend/ s3://<FrontendBucketName>/ --cache-control no-cache --profile sandbox --region ap-southeast-5.
8. TEST. Call the backend URL with curl (POST, JSON body {"prompt":"Say hi in five words"}) and fetch the FrontendUrl with curl. Show the HTTP status and the first lines of each response.
9. REPORT. Finish with a table: Account ID, region, stack status, FrontendUrl, backend URL result, default model, files you changed. List anything I must do myself (for example the Anthropic form or GitHub secrets).
What you will see. Kiro proposes a command, you read it and click Approve. Around the middle a browser tab opens for the sign-in: click Allow. If Kiro asks a question, answer in the chat and it continues. At the end you get a table with your website address. Then go on to Step 4 and Step 6.
Keep the agent on a short leash. Kiro acts with your sandbox permissions. Read every command before you approve it, never paste access keys or tokens into the chat, and do not let it create access keys or change IAM outside your template. Kiro usage also uses your Kiro credits.

When something fails, or you want to understand a result, just ask Kiro:

"The deployment failed. Look at the CloudFormation stack events and the Lambda logs, tell me the root cause, and propose a fix before changing anything."
"The sign-in expired. Run aws sso login --profile sandbox again and continue where you stopped."
Moving an older app to Malaysia. If your deploy.yml or template.yaml still names the Singapore region, change every occurrence to ap-southeast-5 before you deploy. Kiro does this search for you in the prompt above. To do it yourself (from your project folder):
grep -rn "ap-southeast-1" --include=*.yml --include=*.yaml --include=*.py --include=*.html .
Typical places are --region in the sam deploy step, aws-region: in the credentials step, and Bedrock ARNs in template.yaml. Singapore still works for the old app, but your models and guide steps are written for Malaysia.
Lambda Function URLs do not exist in Malaysia. If your template.yaml contains FunctionUrlConfig (older apps), sam deploy fails there. Replace it with an API Gateway HTTP API. Add one resource and change each function:
  AppHttpApi:
    Type: AWS::Serverless::HttpApi
    Properties:
      CorsConfiguration:
        AllowOrigins: ['*']
        AllowHeaders: ['*']
        AllowMethods: [POST, OPTIONS]

  MyFunction:
    Type: AWS::Serverless::Function
    Properties:
      # ...keep everything else, but delete FunctionUrlConfig and AWS_LWA_INVOKE_MODE
      Timeout: 25
      Events:
        Http:
          Type: HttpApi
          Properties:
            ApiId: !Ref AppHttpApi
            Path: /my-widget
            Method: POST

Outputs:
  MyApiUrl:
    Value: !Sub https://${AppHttpApi}.execute-api.${AWS::Region}.amazonaws.com/my-widget
The answer now arrives all at once (no word-by-word streaming) and a request may take at most 30 seconds. The prompt above already tells Kiro to do this for you. You can also ask it: "Replace FunctionUrlConfig with an HTTP API as in the guide."

Optional β€” Automatic Deploys With GitHub Actions

Only if you want every commit to redeploy by itself. Kiro already put your app online in Step 3, so you can skip this.

B1. Create the deploy user, keys and bucket. Click the CloudShell icon (>_) in the top bar of the AWS console, wait for the prompt, and run:

REGION=ap-southeast-5
ACCOUNT=$(aws sts get-caller-identity --query Account --output text)

aws iam create-user --user-name github-deployer
for P in AWSCloudFormationFullAccess AWSLambda_FullAccess IAMFullAccess \
         AmazonAPIGatewayAdministrator AmazonS3FullAccess AmazonBedrockFullAccess; do
  aws iam attach-user-policy --user-name github-deployer \
    --policy-arn arn:aws:iam::aws:policy/$P
done

aws iam create-access-key --user-name github-deployer \
  --query 'AccessKey.[AccessKeyId,SecretAccessKey]' --output text

aws s3 mb s3://sam-deploy-$ACCOUNT-$REGION --region $REGION
The access key line prints the Secret Access Key only once. Copy both values now. Never paste them into chat, email or your code. They go only into GitHub secrets. If IAM user creation is blocked in your account, skip this option: the Kiro deployment in Step 3 does not need IAM users.

B2. Update your GitHub secrets. First see which secrets your workflow uses (search secrets. in deploy.yml on GitHub):

grep -o 'secrets\.[A-Z_]*' .github/workflows/deploy.yml | sort -u

In your repository open Settings β†’ Secrets and variables β†’ Actions. The old secrets already exist, so click the pencil icon β†’ Update on each one (do not create duplicates):

SecretNew value
AWS_ACCESS_KEY_IDThe new Access Key ID from B1
AWS_SECRET_ACCESS_KEYThe new Secret Access Key from B1
SAM_DEPLOY_BUCKETsam-deploy-YOUR-ACCOUNT-ID-ap-southeast-5 (the bucket you just created)
AWS_REGION (only if your workflow uses it)Your region, for example ap-southeast-5
API_KEY_VALUE (only if your workflow uses it)Keep the same value, so your frontend key stays correct

B3. Run the workflow. GitHub β†’ Actions β†’ your workflow β†’ Run workflow. Wait 3–5 minutes for every step to turn green, then open the Print URLs step and copy the API URL and Frontend URL.

Step 4 β€” Check Your Frontend Points to the New Backend

Your website talks to your backend through addresses that are different in every account. There are two cases:

What your files look likeWhat to do
frontend/index.html contains placeholders such as __URL_MY_FUNCTION__, and deploy.yml has sed -i … linesβœ… Nothing. The deployment injects the new addresses by itself every time. (Default setup.)
frontend/index.html contains a real, old address (it looks like https://abc123.execute-api… or https://abc123.lambda-url…on.aws)⚠️ Replace it with the new address, as below.

Kiro checks this for you in Step 3. To check it yourself (from your project folder):

grep -n "__URL_\|__API" frontend/index.html
grep -n "sed -i" .github/workflows/deploy.yml

Find every old value that points to the previous account:

grep -rnE "[0-9]{12}|execute-api|lambda-url|cloudfront.net|s3-website" \
  --include=*.html --include=*.py --include=*.yaml --include=*.yml .

A 12-digit number is usually your old Account ID. execute-api and lambda-url are old backend addresses. (Some matches, like timestamps, are harmless.) Anything that points to the old account must be changed.

To replace an old address, ask Kiro, sharing frontend/index.html:

"Replace the old backend URL(s) in this file with these new ones: [paste the new URLs from the stack outputs]. Keep the API_KEY value as [your API_KEY_VALUE] if the app uses one. Then list any other old AWS URL, bucket name or account ID you find."

Step 5 β€” Restore Data and Extras (If You Use Them)

If your app used…Do this
DynamoDB (chat history, saved results)The table is created empty by your template. Data from the old account is gone. Re-seed it if you need sample data.
HTTPS with CloudFront (main tutorial Step 9)Repeat Step 9. You get a new CloudFront domain, and any custom domain must be pointed at it again.
WAF, alarms, custom domainCreate them again. They do not carry over.
Your team shares one budget per lease, and the account is frozen automatically when spending reaches the limit (for example $18 of $20). Delete anything you do not need, such as idle databases. Your lease card in the Innovation Sandbox web app shows the budget and the days left.

Step 6 β€” Check That Everything Works

If Something Goes Wrong

What you seeWhyFix
explicit deny in a service control policyRegion not allowed, or a blocked model (Opus)Use one of the five regions in Step 1. Use a model from the table in Step 2.
The provided model identifier is invalidThe model is not offered in this region (for example Claude 3 Haiku or Llama 3 in Malaysia)Use a model ID from the table in Step 2
on-demand throughput isn’t supportedPlain model ID usedUse the global. ID from Step 2
Lambda AccessDenied … bedrock:InvokeModel (no mention of a service control policy)Function role lacks Bedrock permissionAdd the policy block from Step 2 to template.yaml
The security token included in the request is invalid / Credentials could not be loadedGitHub still holds keys from the old accountUpdate the two AWS key secrets (Path B, B2). No spaces in the values.
NoSuchBucket / Bucket does not existSAM_DEPLOY_BUCKET still names the old (deleted) bucketUpdate the secret, or use --resolve-s3 (what the Kiro prompt does)
BucketAlreadyExistsS3 names are unique worldwideAdd your Account ID to the bucket name
Stack … is in ROLLBACK_COMPLETE state and can not be updatedA first deploy failed half-wayCloudFormation β†’ select the stack β†’ Delete, wait, then deploy again
Website opens, but requests fail or time outThe frontend still calls an old backend addressDo Step 4
The page shows or calls something like __URL_MY_FUNCTION__The URL-injection lines did not runAsk Kiro: "Inject the stack output URLs into frontend/index.html and upload it again." With GitHub Actions: re-run the workflow.
403 Forbidden from your API (only if your app uses an API key)The key in the frontend does not match API_KEY_VALUECompare both values exactly (no spaces)
explicit deny in a service control policy … p-1lb4bh9n (on every region and every service)You are signed in to an OLD sandbox account that was returned to the pool, not to your current leaseOpen the Innovation Sandbox web app β†’ your Active lease β†’ copy the Account ID. Tell Kiro: "My Account ID is 123456789012. Fix the sandbox profile so it points to this account and sign in again."
anthropic.claude-haiku-5-5 (or sonnet-5-5) is not available for this accountAWS enables the newest Claude models account by account. Nothing is wrong with your appUse Claude Haiku 4.5 (or Nova 2 Lite). Ask your coach to request access from AWS if you really need the newer model
Model use case details have not been submitted for this accountA new account has not filled the one-time Anthropic form. Haiku 4.5 is blocked until the form is filled in the Malaysia region (it may work for a few minutes after the account is created, then stop). Amazon Nova 2 Lite keeps workingUse Nova 2 Lite for now, or open Bedrock in the console, choose a Claude model, click Submit use case details and complete the form (ask your coach for the company details), then wait up to 15 minutes
Kiro: 0 of 1 MCP servers initializedKiro cannot find uvxInstall uv, then use the full path from which uvx / where uvx in mcp.json
The SSO session associated with this profile has expired / Unable to locate credentialsYour terminal sign-in ended, or the profile was never createdTell Kiro: "Run aws sso login --profile sandbox and continue" (if the profile is missing, ask Kiro to set up the sandbox sign-in from Step 3 again)
aws sts get-caller-identity shows an old or unexpected accountThe profile points to your previous accountRe-run aws configure sso and pick the new account, using the profile name sandbox
Console asks you to sign in againThe 1-hour console session endedReopen from eliteacademy.awsapps.com/start
Innovation Sandbox app says Session expiredLogin timed out (or an old tab)Click Login Again, or use the sign-in portal
Everything stops, the account looks frozenThe team budget limit was reachedTell your coach. Delete what you do not need.

Make the Next Expiry Painless