
aws-dynamodb
AWS DynamoDB single-table design, GSI patterns, SDK v3 TypeScript/Python
- Installs
- 522
- Last 24h
- —
- Trending rank
- —
- Security
- Not audited
Install
Works with every agent the skills CLI supports.
npx skills add https://github.com/alinaqi/maggy --skill aws-dynamodb- 01
Install
Run the command in your project. The CLI asks which agents to install for.
- 02
Describe the task
Ask your agent for something this skill covers. It loads aws-dynamodb automatically when relevant.
- 03
Review the result
The agent follows the SKILL.md steps, scripts and references bundled with the skill.
SKILL.md
The instructions your agent reads when the skill is triggered.
AWS DynamoDB Skill
DynamoDB is a fully managed NoSQL database designed for single-digit millisecond performance at any scale. Master single-table design and access pattern modeling.
Sources: DynamoDB Docs | SDK v3 | Best Practices
Core Principle
Design for access patterns, not entities. Think access-pattern-first.
DynamoDB requires you to know your queries before designing your schema. Model around how you'll access data, not how data relates. Single-table design stores multiple entity types in one table using generic key attributes.
Key Concepts
| Concept | Description |
|---|---|
| Partition Key (PK) | Primary key attribute - determines data distribution |
| Sort Key (SK) | Optional secondary key for range queries within partition |
| GSI | Global Secondary Index - alternate partition/sort keys |
| LSI | Local Secondary Index - same partition, different sort |
| Item | Single record (max 400 KB) |
| Attribute | Field within an item |
Single-Table Design
Why Single Table?
- Fetch related data in single query
- Reduce round trips and costs
- Enable transactions across entity types
- Simplify operations (backup, restore, IAM)
Generic Key Pattern
// Instead of entity-specific keys:
// userId, orderId, productId
// Use generic keys that work for all entities:
interface BaseItem {
PK: string; // Partition Key
SK: string; // Sort Key
GSI1PK?: string; // First GSI partition key
GSI1SK?: string; // First GSI sort key
EntityType: string;
// ... entity-specific attributes
}
Example: E-commerce Schema
// Users
{ PK: 'USER#123', SK: 'PROFILE', EntityType: 'User', name: 'John', email: 'john@test.com' }
{ PK: 'USER#123', SK: 'ADDRESS#1', EntityType: 'Address', street: '123 Main', city: 'NYC' }
// Orders for user (1:N relationship)
{ PK: 'USER#123', SK: 'ORDER#2024-001', EntityType: 'Order', total: 99.99, status: 'shipped' }
{ PK: 'USER#123', SK: 'ORDER#2024-002', EntityType: 'Order', total: 49.99, status: 'pending' }
// Order details (query by order ID using GSI)
{ PK: 'USER#123', SK: 'ORDER#2024-001', GSI1PK: 'ORDER#2024-001', GSI1SK: 'ORDER', ... }
{ PK: 'ORDER#2024-001', SK: 'ITEM#1', GSI1PK: 'ORDER#2024-001', GSI1SK: 'ITEM#1', productId: 'PROD#456', qty: 2 }
// Products
{ PK: 'PROD#456', SK: 'PRODUCT', EntityType: 'Product', name: 'Widget', price: 29.99 }
Access Patterns Covered
1. Get user profile → Query PK='USER#123', SK='PROFILE'
2. Get user with addresses → Query PK='USER#123', SK begins_with 'ADDRESS'
3. Get all user orders → Query PK='USER#123', SK begins_with 'ORDER'
4. Get order by ID → Query GSI1, PK='ORDER#2024-001'
5. Get order with items → Query GSI1, PK='ORDER#2024-001'
6. Get product details → Query PK='PROD#456', SK='PRODUCT'
SDK v3 Setup (TypeScript)
Install Dependencies
npm install @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
Client Configuration
// lib/dynamodb.ts
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
const client = new DynamoDBClient({
region: process.env.AWS_REGION || 'us-east-1',
// For local development with DynamoDB Local
...(process.env.DYNAMODB_LOCAL && {
endpoint: 'http://localhost:8000',
credentials: { accessKeyId: 'local', secretAccessKey: 'local' }
})
});
// Document client for simplified operations
export const docClient = DynamoDBDocumentClient.from(client, {
marshallOptions: {
removeUndefinedValues: true, // Important: match v2 behavior
convertClassInstanceToMap: true
},
unmarshallOptions: {
wrapNumbers: false
}
});
export const TABLE_NAME = process.env.DYNAMODB_TABLE || 'MyTable';
Type Definitions
// types/dynamodb.ts
export interface BaseItem {
PK: string;
SK: string;
GSI1PK?: string;
GSI1SK?: string;
EntityType: string;
createdAt: string;
updatedAt: string;
}
export interface User extends BaseItem {
EntityType: 'User';
userId: string;
email: string;
name: string;
}
export interface Order extends BaseItem {
EntityType: 'Order';
orderId: string;
userId: string;
total: number;
status: 'pending' | 'paid' | 'shipped' | 'delivered';
}
// Key builders
export const keys = {
user: (userId: string) => ({
PK: `USER#${userId}`,
SK: 'PROFILE'
}),
userOrders: (userId: string) => ({
PK: `USER#${userId}`,
SKPrefix: 'ORDER#'
}),
order: (userId: string, orderId: string) => ({
PK: `USER#${userId}`,
SK: `ORDER#${orderId}`,
GSI1PK: `ORDER#${orderId}`,
GSI1SK: 'ORDER'
})
};
CRUD Operations
Put Item (Create/Update)
import { PutCommand } from '@aws-sdk/lib-dynamodb';
import { docClient, TABLE_NAME } from './dynamodb';
import { User, keys } from './types';
async function createUser(userId: string, data: { email: string; name: string }): Promise<User> {
const now = new Date().toISOString();
const item: User = {
...keys.user(userId),
EntityType: 'User',
userId,
email: data.email,
name: data.name,
createdAt: now,
updatedAt: now
};
await docClient.send(new PutCommand({
TableName: TABLE_NAME,
Item: item,
ConditionExpression: 'attribute_not_exists(PK)' // Prevent overwrite
}));
return item;
}
Get Item (Read)
import { GetCommand } from '@aws-sdk/lib-dynamodb';
async function getUser(userId: string): Promise<User | null> {
const result = await docClient.send(new GetCommand({
TableName: TABLE_NAME,
Key: keys.user(userId)
}));
return (result.Item as User) || null;
}
Query (List/Search)
import { QueryCommand } from '@aws-sdk/lib-dynamodb';
// Get all orders for a user
async function getUserOrders(userId: string): Promise<Order[]> {
const result = await docClient.send(new QueryCommand({
TableName: TABLE_NAME,
KeyConditionExpression: 'PK = :pk AND begins_with(SK, :sk)',
ExpressionAttributeValues: {
':pk': `USER#${userId}`,
':sk': 'ORDER#'
},
ScanIndexForward: false // Newest first
}));
return (result.Items as Order[]) || [];
}
// Query GSI by order ID
async function getOrderById(orderId: string): Promise<Order | null> {
const result = await docClient.send(new QueryCommand({
TableName: TABLE_NAME,
IndexName: 'GSI1',
KeyConditionExpression: 'GSI1PK = :pk',
ExpressionAttributeValues: {
':pk': `ORDER#${orderId}`
}
}));
return (result.Items?.[0] as Order) || null;
}
// Paginated query
async function getUserOrdersPaginated(
userId: string,
pageSize: number = 20,
lastKey?: Record<string, any>
): Promise<{ items: Order[]; lastKey?: Record<string, any> }> {
const result = await docClient.send(new QueryCommand({
TableName: TABLE_NAME,
KeyConditionExpression: 'PK = :pk AND begins_with(SK, :sk)',
ExpressionAttributeValues: {
':pk': `USER#${userId}`,
':sk': 'ORDER#'
},
Limit: pageSize,
ExclusiveStartKey: lastKey
}));
return {
items: (result.Items as Order[]) || [],
lastKey: result.LastEvaluatedKey
};
}
Update Item
import { UpdateCommand } from '@aws-sdk/lib-dynamodb';
async function updateUser(userId: string, updates: Partial<Pick<User, 'name' | 'email'>>): Promise<User> {
// Build update expression dynamically
const updateParts: string[] = ['#updatedAt = :updatedAt'];
const names: Record<string, string> = { '#updatedAt': 'updatedAt' };
const values: Record<string, any> = { ':updatedAt': new Date().toISOString() };
if (updates.name !== undefined) {
updateParts.push('#name = :name');
names['#name'] = 'name';
values[':name'] = updates.name;
}
if (updates.email !== undefined) {
updateParts.push('#email = :email');
names['#email'] = 'email';
values[':email'] = updates.email;
}
const result = await docClient.send(new UpdateCommand({
TableName: TABLE_NAME,
Key: keys.user(userId),
UpdateExpression: `SET ${updateParts.join(', ')}`,
ExpressionAttributeNames: names,
ExpressionAttributeValues: values,
ReturnValues: 'ALL_NEW',
ConditionExpression: 'attribute_exists(PK)' // Must exist
}));
return result.Attributes as User;
}
// Atomic counter increment
async function incrementOrderCount(userId: string): Promise<void> {
await docClient.send(new UpdateCommand({
TableName: TABLE_NAME,
Key: keys.user(userId),
UpdateExpression: 'SET orderCount = if_not_exists(orderCount, :zero) + :inc',
ExpressionAttributeValues: {
':zero': 0,
':inc': 1
}
}));
}
Delete Item
import { DeleteCommand } from '@aws-sdk/lib-dynamodb';
async function deleteUser(userId: string): Promise<void> {
await docClient.send(new DeleteCommand({
TableName: TABLE_NAME,
Key: keys.user(userId),
ConditionExpression: 'attribute_exists(PK)'
}));
}
Batch Operations
Batch Write (Up to 25 items)
import { BatchWriteCommand } from '@aws-sdk/lib-dynamodb';
async function batchCreateItems(items: BaseItem[]): Promise<void> {
// DynamoDB allows max 25 items per batch
const chunks = [];
for (let i = 0; i < items.length; i += 25) {
chunks.push(items.slice(i, i + 25));
}
for (const chunk of chunks) {
await docClient.send(new BatchWriteCommand({
RequestItems: {
[TABLE_NAME]: chunk.map(item => ({
PutRequest: { Item: item }
}))
}
}));
}
}
Batch Get (Up to 100 items)
import { BatchGetCommand } from '@aws-sdk/lib-dynamodb';
async function batchGetUsers(userIds: string[]): Promise<User[]> {
const result = await docClient.send(new BatchGetCommand({
RequestItems: {
[TABLE_NAME]: {
Keys: userIds.map(id => keys.user(id))
}
}
}));
return (result.Responses?.[TABLE_NAME] as User[]) || [];
}
Transactions
TransactWrite (Atomic Multi-Item)
import { TransactWriteCommand } from '@aws-sdk/lib-dynamodb';
async function createOrderWithItems(
userId: string,
orderId: string,
orderData: { total: number },
items: { productId: string; quantity: number }[]
): Promise<void> {
const now = new Date().toISOString();
const transactItems = [
// Create order
{
Put: {
TableName: TABLE_NAME,
Item: {
...keys.order(userId, orderId),
EntityType: 'Order',
orderId,
userId,
total: orderData.total,
status: 'pending',
createdAt: now,
updatedAt: now
},
ConditionExpression: 'attribute_not_exists(PK)'
}
},
// Update user's order count
{
Update: {
TableName: TABLE_NAME,
Key: keys.user(userId),
UpdateExpression: 'SET orderCount = if_not_exists(orderCount, :zero) + :inc',
ExpressionAttributeValues: { ':zero': 0, ':inc': 1 }
}
},
// Add order items
...items.map((item, index) => ({
Put: {
TableName: TABLE_NAME,
Item: {
PK: `ORDER#${orderId}`,
SK: `ITEM#${index}`,
GSI1PK: `ORDER#${orderId}`,
GSI1SK: `ITEM#${index}`,
EntityType: 'OrderItem',
productId: item.productId,
quantity: item.quantity,
createdAt: now
}
}
}))
];
await docClient.send(new TransactWriteCommand({
TransactItems: transactItems
}));
}
GSI Patterns
Sparse Index
// Only items with GSI1PK attribute appear in the index
// Useful for "featured" or "flagged" items
// Featured products (only some products have GSI1PK)
{ PK: 'PROD#1', SK: 'PRODUCT', GSI1PK: 'FEATURED', GSI1SK: 'PROD#1', ... } // In index
{ PK: 'PROD#2', SK: 'PRODUCT', ... } // Not in index (no GSI1PK)
// Query featured products
const featured = await docClient.send(new QueryCommand({
TableName: TABLE_NAME,
IndexName: 'GSI1',
KeyConditionExpression: 'GSI1PK = :pk',
ExpressionAttributeValues: { ':pk': 'FEATURED' }
}));
Inverted Index (GSI)
// Main table: User -> Orders (PK=USER#, SK=ORDER#)
// GSI: Orders by status (GSI1PK=STATUS#, GSI1SK=ORDER#)
{ PK: 'USER#123', SK: 'ORDER#001', GSI1PK: 'STATUS#pending', GSI1SK: 'ORDER#001', ... }
{ PK: 'USER#456', SK: 'ORDER#002', GSI1PK: 'STATUS#shipped', GSI1SK: 'ORDER#002', ... }
// Get all pending orders across all users
const pending = await docClient.send(new QueryCommand({
TableName: TABLE_NAME,
IndexName: 'GSI1',
KeyConditionExpression: 'GSI1PK = :pk',
ExpressionAttributeValues: { ':pk': 'STATUS#pending' }
}));
Multi-Attribute Composite Keys (Nov 2025+)
// New feature: Up to 4 attributes per partition/sort key
// No more synthetic keys like "TOURNAMENT#WINTER2024#REGION#NA-EAST"
// Table definition (IaC)
const table = {
AttributeDefinitions: [
{ AttributeName: 'tournament', AttributeType: 'S' },
{ AttributeName: 'region', AttributeType: 'S' },
{ AttributeName: 'score', AttributeType: 'N' }
],
GlobalSecondaryIndexes: [{
IndexName: 'TournamentRegionIndex',
KeySchema: [
{ AttributeName: 'tournament', KeyType: 'HASH' }, // Composite PK part 1
{ AttributeName: 'region', KeyType: 'HASH' }, // Composite PK part 2
{ AttributeName: 'score', KeyType: 'RANGE' }
]
}]
};
Python (boto3)
Setup
# requirements.txt
boto3>=1.34.0
# db.py
import boto3
from boto3.dynamodb.conditions import Key, Attr
import os
dynamodb = boto3.resource(
'dynamodb',
region_name=os.getenv('AWS_REGION', 'us-east-1'),
endpoint_url=os.getenv('DYNAMODB_LOCAL_ENDPOINT') # For local dev
)
table = dynamodb.Table(os.getenv('DYNAMODB_TABLE', 'MyTable'))
Operations
from datetime import datetime
from typing import Optional, List
from decimal import Decimal
def create_user(user_id: str, email: str, name: str) -> dict:
now = datetime.utcnow().isoformat()
item = {
'PK': f'USER#{user_id}',
'SK': 'PROFILE',
'EntityType': 'User',
'userId': user_id,
'email': email,
'name': name,
'createdAt': now,
'updatedAt': now
}
table.put_item(
Item=item,
ConditionExpression='attribute_not_exists(PK)'
)
return item
def get_user(user_id: str) -> Optional[dict]:
response = table.get_item(
Key={'PK': f'USER#{user_id}', 'SK': 'PROFILE'}
)
return response.get('Item')
def get_user_orders(user_id: str) -> List[dict]:
response = table.query(
KeyConditionExpression=Key('PK').eq(f'USER#{user_id}') & Key('SK').begins_with('ORDER#'),
ScanIndexForward=False
)
return response.get('Items', [])
def update_user(user_id: str, **updates) -> dict:
update_parts = ['#updatedAt = :updatedAt']
names = {'#updatedAt': 'updatedAt'}
values = {':updatedAt': datetime.utcnow().isoformat()}
for key, value in updates.items():
update_parts.append(f'#{key} = :{key}')
names[f'#{key}'] = key
values[f':{key}'] = value
response = table.update_item(
Key={'PK': f'USER#{user_id}', 'SK': 'PROFILE'},
UpdateExpression=f'SET {", ".join(update_parts)}',
ExpressionAttributeNames=names,
ExpressionAttributeValues=values,
ReturnValues='ALL_NEW'
)
return response['Attributes']
def delete_user(user_id: str) -> None:
table.delete_item(
Key={'PK': f'USER#{user_id}', 'SK': 'PROFILE'}
)
Local Development
DynamoDB Local
# Docker
docker run -d -p 8000:8000 amazon/dynamodb-local
# Create table locally
aws dynamodb create-table \
--endpoint-url http://localhost:8000 \
--table-name MyTable \
--attribute-definitions \
AttributeName=PK,AttributeType=S \
AttributeName=SK,AttributeType=S \
AttributeName=GSI1PK,AttributeType=S \
AttributeName=GSI1SK,AttributeType=S \
--key-schema \
AttributeName=PK,KeyType=HASH \
AttributeName=SK,KeyType=RANGE \
--global-secondary-indexes \
'IndexName=GSI1,KeySchema=[{AttributeName=GSI1PK,KeyType=HASH},{AttributeName=GSI1SK,KeyType=RANGE}],Projection={ProjectionType=ALL}' \
--billing-mode PAY_PER_REQUEST
NoSQL Workbench
AWS provides NoSQL Workbench for visual data modeling and querying.
CLI Quick Reference
# Table operations
aws dynamodb create-table --cli-input-json file://table.json
aws dynamodb describe-table --table-name MyTable
aws dynamodb delete-table --table-name MyTable
# Item operations
aws dynamodb put-item --table-name MyTable --item '{"PK":{"S":"USER#1"},"SK":{"S":"PROFILE"}}'
aws dynamodb get-item --table-name MyTable --key '{"PK":{"S":"USER#1"},"SK":{"S":"PROFILE"}}'
aws dynamodb delete-item --table-name MyTable --key '{"PK":{"S":"USER#1"},"SK":{"S":"PROFILE"}}'
# Query
aws dynamodb query --table-name MyTable \
--key-condition-expression "PK = :pk" \
--expression-attribute-values '{":pk":{"S":"USER#1"}}'
# Scan (avoid in production)
aws dynamodb scan --table-name MyTable --limit 10
Anti-Patterns
- Scan operations - Always use Query with proper key conditions
- Hot partitions - Distribute writes with high-cardinality partition keys
- Large items - Keep items under 400KB; use S3 for large data
- Too many GSIs - Each GSI duplicates data; design carefully
- Ignoring capacity - Monitor consumed capacity, use on-demand for variable loads
- No condition expressions - Always validate with ConditionExpression
- Fetching all attributes - Use ProjectionExpression to limit data
- Multi-table design without reason - Single-table is preferred unless access patterns don't overlap
FAQ
What does the aws-dynamodb skill do?
AWS DynamoDB single-table design, GSI patterns, SDK v3 TypeScript/Python
How do I install aws-dynamodb?
Run npx skills add https://github.com/alinaqi/maggy --skill aws-dynamodb in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/aws-dynamodb/ (personal) or .claude/skills/aws-dynamodb/ (project).
Which AI agents can use aws-dynamodb?
Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.
Is aws-dynamodb safe to install?
No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.
Who maintains aws-dynamodb?
It is published by alinaqi in alinaqi/maggy. It has 522 recorded installs.
More from alinaqi/maggy
pwa-development
alinaqi/maggy
Progressive Web Apps - service workers, caching strategies, offline, Workbox
playwright-testing
alinaqi/maggy
E2E testing with Playwright - Page Objects, cross-browser, CI/CD
android-kotlin
alinaqi/maggy
Android Kotlin development with Coroutines, Jetpack Compose, Hilt, and MockK testing
code-review
alinaqi/maggy
Mandatory code reviews via /code-review before commits and deploys
ui-mobile
alinaqi/maggy
Mobile UI patterns - React Native, iOS/Android, touch targets
posthog-analytics
alinaqi/maggy
PostHog analytics, event tracking, feature flags, dashboards
Popular devops & cloud skills
microsoft-foundry
microsoft/azure-skills
Build, deploy, evaluate, optimize, fine-tune, and manage Microsoft Foundry agents, models, and resources end to end. USE FOR: foundry, azd ai agent, azd provision/deploy, hosted agent scaffold/develop/run/deploy/troubleshoot, prompt agent create, managed harness agent create, GitHub Copilot harness agent, create agent, update agent, add tool to agent, invoke agent, agent insights, evaluate agent, batch eval, continuous eval, continuous monitoring, agent CI/CD, optimize prompt, improve prompt, optimize agent instructions, Agent Optimizer scaffold, dataset curation from traces, deploy model, model fine-tuning (SFT/DPO/RFT), Foundry project, RBAC, role assignment, permissions, quota, capacity, region, deployment failure, AI Services, create Foundry resource, knowledge index, customize deployment, onboard, availability, training-data, grader, distillation, large file upload. DO NOT USE FOR: Azure Functions, App Service, general Azure deploy (use azure-deploy), general Azure prep (use azure
azure-diagnostics
microsoft/azure-skills
Debug Azure production issues on Azure using AppLens, Azure Monitor, resource health, and safe triage. WHEN: debug production issues, troubleshoot app service, app service high CPU, app service deployment failure, troubleshoot container apps, container app 503 or target port mismatch, troubleshoot functions, function telemetry missing or stopped after a config/connection-string change, troubleshoot AKS, VM RDP, Linux SSH, VM black screen, can't connect to VM, reset VM password, NSG or firewall blocking, kubectl cannot connect, kube-system/CoreDNS failures, pod pending, crashloop, node not ready, upgrade failures, analyze logs, KQL, insights, image pull failures, cold start issues, health probe failures, resource health, root cause of errors, troubleshoot event hubs, troubleshoot service bus, messaging SDK error, AMQP connection failure, message lock lost, lock renewal during long-running processing, service bus dead letter.
azure-prepare
microsoft/azure-skills
Prepare azd-based Azure projects for deployment: generates azure.yaml, infrastructure (Bicep/Terraform), and Dockerfiles for the Azure Developer CLI (azd) workflow. USE ONLY when the user explicitly wants to use azd as the deployment tool, or the project already has an azure.yaml file. DO NOT USE FOR: non-azd deployments, Python App Service code-only deploys (use python-appservice-deploy), or cross-cloud migration (use azure-cloud-migrate). WHEN: prepare app for azd, create azure.yaml, set up azd infrastructure, modernize app for Azure with azd, deploy with azd, function app, timer trigger, service bus trigger, event-driven function, managed identity, generate Bicep, generate Terraform, create and deploy to Azure.
azure-ai
microsoft/azure-skills
Use for Azure AI: Search, Speech, OpenAI, Document Intelligence. Helps with search, vector/hybrid search, speech-to-text, text-to-speech, transcription, OCR. WHEN: AI Search, query search, vector search, hybrid search, semantic search, speech-to-text, text-to-speech, transcribe, OCR, convert text to speech.
azure-deploy
microsoft/azure-skills
Execute Azure deployments for ALREADY-PREPARED applications that have existing .azure/deployment-plan.md and infrastructure files. DO NOT use this skill when the user asks to CREATE a new application — use azure-prepare instead. This skill runs azd up, azd deploy, terraform apply, and az deployment commands with built-in error recovery. Requires .azure/deployment-plan.md from azure-prepare and validated status from azure-validate. WHEN: "run azd up", "run azd deploy", "execute deployment", "push to production", "push to cloud", "go live", "ship it", "bicep deploy", "terraform apply", "publish to Azure", "launch on Azure". DO NOT USE WHEN: "create and deploy", "build and deploy", "create a new app", "set up infrastructure", "create and deploy to Azure using Terraform" — use azure-prepare for these.
azure-validate
microsoft/azure-skills
Pre-deployment validation for Azure readiness. Run deep checks on configuration, infrastructure (Bicep or Terraform), RBAC role assignments, managed identity permissions, and prerequisites before deploying. WHEN: validate my app, check deployment readiness, run preflight checks, verify configuration, check if ready to deploy, validate azure.yaml, validate Bicep, test before deploying, troubleshoot deployment errors, validate Azure Functions, validate function app, validate serverless deployment, verify RBAC roles, check role assignments, review managed identity permissions, what-if analysis, validate Container Apps deployment.