diff --git a/alchemy-web/src/content/docs/guides/azure.mdx b/alchemy-web/src/content/docs/guides/azure.mdx new file mode 100644 index 000000000..aad38888f --- /dev/null +++ b/alchemy-web/src/content/docs/guides/azure.mdx @@ -0,0 +1,420 @@ +--- +title: Azure +description: Deploy your first serverless application to Microsoft Azure using Alchemy +sidebar: + order: 3 +--- + +import { Tabs, TabItem } from '@astrojs/starlight/components'; + +This guide walks you through deploying a serverless function with blob storage to Microsoft Azure using Alchemy. + +## Prerequisites + +Before you begin, you'll need: + +- An [Azure account](https://azure.microsoft.com/free/) +- Azure CLI installed on your machine +- Node.js 20+ or Bun installed + +## Install Azure CLI + +Install the Azure CLI for local development: + + + + ```sh + brew install azure-cli + ``` + + + ```sh + winget install Microsoft.AzureCLI + ``` + + + ```sh + curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash + ``` + + + +## Install Alchemy + +Install Alchemy in your project: + + + + ```sh + bun add alchemy + ``` + + + ```sh + npm install alchemy + ``` + + + ```sh + pnpm add alchemy + ``` + + + ```sh + yarn add alchemy + ``` + + + +## Set Up Azure Credentials + +### 1. Log in to Azure CLI + +```sh +az login +``` + +This will open a browser window for authentication. + +### 2. Get Your Subscription ID + +```sh +az account show --query id --output tsv +``` + +Copy this subscription ID - you'll need it in your `.env` file. + +### 3. Create a Service Principal + +Create a service principal for Alchemy to use: + +```sh +az ad sp create-for-rbac --name "alchemy-deploy" --role contributor \ + --scopes /subscriptions/{your-subscription-id} +``` + +Replace `{your-subscription-id}` with your actual subscription ID from step 2. + +This command will output credentials like: + +```json +{ + "appId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "displayName": "alchemy-deploy", + "password": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "tenant": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +### 4. Create `.env` File + +Create a `.env` file in your project root with the credentials: + +```env +AZURE_SUBSCRIPTION_ID=your-subscription-id +AZURE_TENANT_ID=your-tenant-id +AZURE_CLIENT_ID=your-app-id +AZURE_CLIENT_SECRET=your-password +``` + +Map the values from the service principal output: +- `AZURE_SUBSCRIPTION_ID` - From step 2 +- `AZURE_TENANT_ID` - The `tenant` value +- `AZURE_CLIENT_ID` - The `appId` value +- `AZURE_CLIENT_SECRET` - The `password` value + +:::caution +Never commit your `.env` file to version control! Add it to your `.gitignore`: + +```sh +echo ".env" >> .gitignore +``` +::: + +### 5. Register Required Resource Providers + +Some Azure services require explicit resource provider registration before first use. Register the providers you plan to use: + +```sh +# Register SQL Database provider (required for SQL Server/Database) +az provider register --namespace Microsoft.Sql + +# Register Cognitive Services provider (required for AI services) +az provider register --namespace Microsoft.CognitiveServices +``` + +Check registration status (may take a few minutes): + +```sh +az provider show -n Microsoft.Sql --query "registrationState" +az provider show -n Microsoft.CognitiveServices --query "registrationState" +``` + +Wait until the state shows `"Registered"` before creating resources that depend on these providers. + +:::note +Most Azure services auto-register their providers when you create resources through the Azure Portal. However, when using Infrastructure as Code tools like Alchemy, you may need to register providers manually. +::: + +## Create Your First Azure Application + +Create an `alchemy.run.ts` file to define your infrastructure: + +```typescript +import { alchemy } from "alchemy"; +import * as azure from "alchemy/azure"; + +const app = await alchemy("my-azure-app", { + azure: { + subscriptionId: process.env.AZURE_SUBSCRIPTION_ID!, + tenantId: alchemy.secret.env.AZURE_TENANT_ID, + clientId: alchemy.secret.env.AZURE_CLIENT_ID, + clientSecret: alchemy.secret.env.AZURE_CLIENT_SECRET, + } +}); + +// Create a resource group in East US +const rg = await azure.ResourceGroup("my-rg", { + location: "eastus" +}); + +// Create a storage account +const storage = await azure.StorageAccount("storage", { + resourceGroup: rg, + location: "eastus" +}); + +// Create a blob container for uploads +const uploads = await azure.BlobContainer("uploads", { + resourceGroup: rg, + storageAccount: storage +}); + +// Create a Function App for serverless API +const api = await azure.FunctionApp("api", { + resourceGroup: rg, + location: "eastus", + runtime: "node", + runtimeVersion: "20", + appSettings: { + STORAGE_CONNECTION_STRING: storage.connectionString, + UPLOAD_CONTAINER: uploads.name + } +}); + +console.log(`Function App URL: ${api.url}`); +console.log(`Storage Account: ${storage.name}`); +console.log(`Container URL: ${uploads.url}`); + +await app.finalize(); +``` + +This creates: +- A **Resource Group** to organize your resources +- A **Storage Account** for blob storage +- A **Blob Container** for file uploads +- A **Function App** for your serverless API + +## Deploy to Azure + +Run your `alchemy.run.ts` script to deploy: + + + + ```sh + bun ./alchemy.run.ts + ``` + + + ```sh + npx tsx ./alchemy.run.ts + ``` + + + ```sh + pnpm tsx ./alchemy.run.ts + ``` + + + ```sh + yarn tsx ./alchemy.run.ts + ``` + + + +You should see output like: + +```sh +Creating resource group my-rg in eastus... +Creating storage account... +Creating blob container uploads... +Creating function app api... +Function App URL: https://my-azure-app-prod-api.azurewebsites.net +Storage Account: myazureappprodst +Container URL: https://myazureappprodst.blob.core.windows.net/uploads +``` + +:::note +Azure resource creation can take a few minutes, especially for Function Apps and Storage Accounts. +::: + +## Verify Your Deployment + +You can verify your deployment in the [Azure Portal](https://portal.azure.com): + +1. Navigate to **Resource Groups** +2. Find your resource group (e.g., `my-azure-app-prod-my-rg`) +3. You should see: + - Storage Account + - Function App + - App Service Plan + +## Update Your Infrastructure + +To update your infrastructure, simply modify `alchemy.run.ts` and run it again: + +```typescript +// Add tags to your resource group +const rg = await azure.ResourceGroup("my-rg", { + location: "eastus", + tags: { + environment: "production", + team: "platform" + } +}); + +// Add more app settings to your Function App +const api = await azure.FunctionApp("api", { + resourceGroup: rg, + location: "eastus", + runtime: "node", + runtimeVersion: "20", + appSettings: { + STORAGE_CONNECTION_STRING: storage.connectionString, + UPLOAD_CONTAINER: uploads.name, + NODE_ENV: "production", + LOG_LEVEL: "info" + } +}); +``` + +Run the script again to apply updates: + + + + ```sh + bun ./alchemy.run.ts + ``` + + + ```sh + npx tsx ./alchemy.run.ts + ``` + + + ```sh + pnpm tsx ./alchemy.run.ts + ``` + + + ```sh + yarn tsx ./alchemy.run.ts + ``` + + + +## Tear Down + +When you're done, tear down all resources: + + + + ```sh + bun ./alchemy.run.ts --destroy + ``` + + + ```sh + npx tsx ./alchemy.run.ts --destroy + ``` + + + ```sh + pnpm tsx ./alchemy.run.ts --destroy + ``` + + + ```sh + yarn tsx ./alchemy.run.ts --destroy + ``` + + + +This will delete the resource group and all resources within it. + +:::tip +Deleting a Resource Group automatically deletes all resources inside it. This makes cleanup simple! +::: + +## Next Steps + +Now that you have a basic Azure application running, you can: + +- **Add a Database**: Use [CosmosDB](/providers/azure/cosmosdb-account/) or [SQL Database](/providers/azure/sql-database/) +- **Deploy a Static Website**: Use [Static Web App](/providers/azure/static-web-app/) +- **Add Networking**: Create a [Virtual Network](/providers/azure/virtual-network/) for isolation +- **Secure Secrets**: Use [Key Vault](/providers/azure/key-vault/) for secret management +- **Use Managed Identity**: Add [User Assigned Identity](/providers/azure/user-assigned-identity/) for secure access + +## Common Issues + +### "Unauthorized" or "Invalid Credentials" + +Make sure your `.env` file has the correct credentials and that your service principal has the `contributor` role. + +### "Location not available" + +Some Azure regions may not support all resource types. Try using `eastus` or `westus2` instead. + +### "Resource Already Exists" + +If a resource with the same name already exists, use the `adopt: true` flag: + +```typescript +const rg = await azure.ResourceGroup("my-rg", { + location: "eastus", + adopt: true +}); +``` + +### "Quota Exceeded" + +Free tier Azure subscriptions have resource limits. Check your [Azure Quotas](https://portal.azure.com/#view/Microsoft_Azure_Capacity/QuotaMenuBlade) in the portal. + +### "Resource Provider Not Registered" + +If you receive an error like `The subscription is not registered to use namespace 'Microsoft.Sql'`, you need to register the resource provider: + +```sh +az provider register --namespace Microsoft.Sql +``` + +Common providers that may require registration: +- `Microsoft.Sql` - For SQL Server and SQL Database +- `Microsoft.CognitiveServices` - For AI and Cognitive Services +- `Microsoft.ContainerInstance` - For Container Instances (usually auto-registers) +- `Microsoft.Web` - For App Service, Function App, Static Web App (usually auto-registers) + +Check registration status: +```sh +az provider show -n Microsoft.Sql --query "registrationState" +``` + +Wait for the state to show `"Registered"` before deploying resources. + +## Additional Resources + +- [Azure Provider Documentation](/providers/azure/) +- [Azure Example Projects](https://github.com/alchemy-run/alchemy/tree/main/examples) +- [Azure Portal](https://portal.azure.com) +- [Azure Documentation](https://docs.microsoft.com/azure) diff --git a/alchemy-web/src/content/docs/providers/azure/app-service.md b/alchemy-web/src/content/docs/providers/azure/app-service.md new file mode 100644 index 000000000..59274a2f8 --- /dev/null +++ b/alchemy-web/src/content/docs/providers/azure/app-service.md @@ -0,0 +1,388 @@ +--- +title: AppService +description: Azure App Service - PaaS web hosting for containers and code +--- + +# AppService + +Azure App Service is a fully managed platform for building, deploying, and scaling web apps. It's equivalent to AWS Elastic Beanstalk and supports multiple languages and frameworks without managing infrastructure. + +Key features: +- **Fully managed** - no server management required +- **Multiple runtimes** - Node.js, Python, .NET, Java, PHP, Ruby +- **Built-in autoscaling** based on demand +- **Deployment slots** for staging and blue-green deployments +- **CI/CD integration** with Azure DevOps and GitHub Actions +- **Custom domains** and SSL certificates +- **VNet integration** for private connectivity + +## Properties + +### Input Properties + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| `name` | `string` | No | Name of the app service. Must be 2-60 characters, alphanumeric and hyphens only. Must be globally unique (creates `{name}.azurewebsites.net`). Defaults to `${app}-${stage}-${id}` | +| `resourceGroup` | `string \| ResourceGroup` | Yes | The resource group to create this app service in | +| `location` | `string` | No | Azure region for the app service. Defaults to the resource group's location | +| `sku` | `string` | No | The pricing tier. Options: `F1` (Free), `D1` (Shared), `B1-B3` (Basic), `S1-S3` (Standard), `P1V2-P3V2` (Premium V2), `P1V3-P3V3` (Premium V3). Defaults to `B1` | +| `runtime` | `string` | No | The runtime stack. Options: `node`, `python`, `dotnet`, `java`, `php`, `ruby`. Defaults to `node` | +| `runtimeVersion` | `string` | No | Runtime version (e.g., `"18"`, `"20"` for Node.js, `"3.9"`, `"3.11"` for Python). Defaults to `"20"` for Node.js | +| `os` | `string` | No | Operating system. Options: `linux`, `windows`. Defaults to `linux` | +| `identity` | `UserAssignedIdentity` | No | User-assigned managed identity for secure access to Azure resources | +| `appSettings` | `Record` | No | Application settings (environment variables) | +| `httpsOnly` | `boolean` | No | Enable HTTPS only (redirect HTTP to HTTPS). Defaults to `true` | +| `alwaysOn` | `boolean` | No | Keep the app loaded even when idle (not available on Free tier). Defaults to `true` | +| `localMySqlEnabled` | `boolean` | No | Enable local MySQL in-app database (Windows only). Defaults to `false` | +| `ftpsState` | `string` | No | FTP deployment state. Options: `AllAllowed`, `FtpsOnly`, `Disabled`. Defaults to `Disabled` | +| `minTlsVersion` | `string` | No | Minimum TLS version. Options: `1.0`, `1.1`, `1.2`, `1.3`. Defaults to `1.2` | +| `tags` | `Record` | No | Tags to apply to the app service | +| `adopt` | `boolean` | No | Whether to adopt an existing app service. Defaults to `false` | +| `delete` | `boolean` | No | Whether to delete the app service when removed from Alchemy. Defaults to `true` | + +### Output Properties + +All input properties plus: + +| Property | Type | Description | +|----------|------|-------------| +| `id` | `string` | The Alchemy resource ID | +| `defaultHostname` | `string` | The default hostname (e.g., `my-app-service.azurewebsites.net`) | +| `url` | `string` | The app service URL (e.g., `https://my-app-service.azurewebsites.net`) | +| `outboundIpAddresses` | `string` | The outbound IP addresses | +| `possibleOutboundIpAddresses` | `string` | The possible outbound IP addresses | +| `type` | `"azure::AppService"` | Resource type identifier | + +## Usage + +### Basic App Service + +Create a Node.js app service on Linux: + +```typescript +import { alchemy } from "alchemy"; +import { ResourceGroup, AppService } from "alchemy/azure"; + +const app = await alchemy("my-app", { + azure: { + subscriptionId: process.env.AZURE_SUBSCRIPTION_ID! + } +}); + +const rg = await ResourceGroup("main", { + location: "eastus" +}); + +const appService = await AppService("web", { + resourceGroup: rg, + runtime: "node", + runtimeVersion: "20", + sku: "B1" +}); + +console.log(`App Service URL: ${appService.url}`); + +await app.finalize(); +``` + +### App Service with Managed Identity + +Use managed identity to securely access other Azure resources: + +```typescript +const identity = await UserAssignedIdentity("app-identity", { + resourceGroup: rg +}); + +const appService = await AppService("secure-web", { + resourceGroup: rg, + runtime: "node", + identity: identity, + sku: "S1" +}); + +// The app service can now access other Azure resources +// without storing connection strings or keys +``` + +### App Service with App Settings + +Configure environment variables and secrets: + +```typescript +const appService = await AppService("configured-web", { + resourceGroup: rg, + runtime: "node", + sku: "B1", + appSettings: { + NODE_ENV: "production", + DATABASE_URL: alchemy.secret.env.DATABASE_URL, + API_KEY: alchemy.secret.env.API_KEY, + CUSTOM_SETTING: "value" + } +}); + +// App settings are available as environment variables +// Secrets are encrypted in the Alchemy state file +``` + +### Python App Service + +Create a Python web application: + +```typescript +const pythonApp = await AppService("python-web", { + resourceGroup: rg, + runtime: "python", + runtimeVersion: "3.11", + os: "linux", + sku: "B1" +}); + +// Deploy Flask, Django, FastAPI, or any Python web framework +``` + +### .NET App Service + +Create a .NET web application: + +```typescript +const dotnetApp = await AppService("dotnet-web", { + resourceGroup: rg, + runtime: "dotnet", + runtimeVersion: "8.0", + os: "linux", + sku: "B1" +}); + +// Deploy ASP.NET Core applications +``` + +### Premium App Service + +Use Premium tier for production workloads: + +```typescript +const premiumApp = await AppService("prod-web", { + resourceGroup: rg, + runtime: "node", + runtimeVersion: "20", + sku: "P1V3", // Premium V3 + alwaysOn: true, + httpsOnly: true, + minTlsVersion: "1.3" +}); + +// Premium features: +// - VNet integration +// - Better performance +// - More memory and CPU +// - Advanced scaling options +``` + +### Windows App Service + +Create a Windows-based app service: + +```typescript +const windowsApp = await AppService("windows-web", { + resourceGroup: rg, + runtime: "dotnet", + runtimeVersion: "8.0", + os: "windows", + sku: "B1", + localMySqlEnabled: true // Windows-only feature +}); +``` + +### Multi-Region Deployment + +Deploy app services across multiple regions: + +```typescript +const eastRg = await ResourceGroup("east", { location: "eastus" }); +const westRg = await ResourceGroup("west", { location: "westus" }); + +const eastApp = await AppService("east-web", { + resourceGroup: eastRg, + runtime: "node", + sku: "S1" +}); + +const westApp = await AppService("west-web", { + resourceGroup: westRg, + runtime: "node", + sku: "S1" +}); + +// Use Azure Traffic Manager or Front Door to distribute traffic +``` + +### Adopt Existing App Service + +Adopt and manage an existing app service: + +```typescript +const existingApp = await AppService("existing-web", { + name: "my-existing-app-service", + resourceGroup: rg, + runtime: "node", + sku: "B1", + adopt: true +}); + +// The app service is now managed by Alchemy +``` + +## Pricing Tiers + +| Tier | Type | Features | Use Case | +|------|------|----------|----------| +| **F1** | Free | 1GB disk, 60 CPU mins/day, no Always On | Development, testing | +| **D1** | Shared | 1GB disk, 240 CPU mins/day, no Always On | Small personal projects | +| **B1-B3** | Basic | Dedicated instances, custom domains, SSL | Small production apps | +| **S1-S3** | Standard | Autoscaling, staging slots, daily backups | Production apps | +| **P1V2-P3V2** | Premium V2 | Better performance, VNet integration | High-traffic production | +| **P1V3-P3V3** | Premium V3 | Latest hardware, best performance | Enterprise apps | + +## Runtime Versions + +### Node.js (Linux) +- `18`: Node.js 18 LTS +- `20`: Node.js 20 LTS (recommended) + +### Python (Linux) +- `3.9`: Python 3.9 +- `3.10`: Python 3.10 +- `3.11`: Python 3.11 (recommended) +- `3.12`: Python 3.12 + +### .NET (Linux/Windows) +- `6.0`: .NET 6 LTS +- `8.0`: .NET 8 LTS (recommended) + +### Java (Linux) +- `11`: Java 11 LTS +- `17`: Java 17 LTS +- `21`: Java 21 LTS + +### PHP (Linux) +- `8.0`: PHP 8.0 +- `8.1`: PHP 8.1 +- `8.2`: PHP 8.2 + +## Important Notes + +### Global Naming + +App service names must be globally unique across all of Azure because they create a `{name}.azurewebsites.net` subdomain. + +### Immutable Properties + +The following properties cannot be changed after creation: +- `name` - changing the name creates a new app service +- `location` - changing the location creates a new app service + +### Always On + +The `alwaysOn` property keeps your app loaded even when idle, preventing cold starts. It's: +- **Not available** on Free (F1) tier +- **Recommended** for production apps +- **Default**: `true` (except on Free tier) + +### HTTPS Only + +By default, app services redirect HTTP traffic to HTTPS (`httpsOnly: true`). This is a security best practice and recommended for all production apps. + +### Operating System + +- **Linux**: More cost-effective, supports containers, better for Node.js/Python +- **Windows**: Required for .NET Framework, supports in-app MySQL + +## Common Patterns + +### Express.js API + +```typescript +const api = await AppService("express-api", { + resourceGroup: rg, + runtime: "node", + runtimeVersion: "20", + sku: "B1", + appSettings: { + NODE_ENV: "production", + PORT: "8080" + } +}); + +// Deploy Express.js app +// App listens on process.env.PORT +``` + +### Django Application + +```typescript +const djangoApp = await AppService("django-app", { + resourceGroup: rg, + runtime: "python", + runtimeVersion: "3.11", + os: "linux", + sku: "B1", + appSettings: { + DJANGO_SETTINGS_MODULE: "myproject.settings", + SECRET_KEY: alchemy.secret.env.DJANGO_SECRET_KEY + } +}); +``` + +### ASP.NET Core Web App + +```typescript +const aspnetApp = await AppService("aspnet-web", { + resourceGroup: rg, + runtime: "dotnet", + runtimeVersion: "8.0", + os: "linux", + sku: "S1", + alwaysOn: true +}); +``` + +### Container Deployment + +```typescript +const containerApp = await AppService("container-web", { + resourceGroup: rg, + runtime: "node", // Base runtime + os: "linux", + sku: "B1" +}); + +// Configure container settings separately +// Or use Azure Container Apps for better container support +``` + +## Deployment + +Deploy your application using: +- **Azure CLI**: `az webapp up` +- **GitHub Actions**: Built-in deployment workflow +- **Azure DevOps**: Azure Pipelines +- **FTP/FTPS**: Direct file upload (if enabled) +- **Local Git**: Push to Azure remote +- **ZIP Deploy**: Upload zip file + +## Related Resources + +- [ResourceGroup](./resource-group) - Logical container for Azure resources +- [UserAssignedIdentity](./user-assigned-identity) - Managed identity for secure access +- [FunctionApp](./function-app) - Serverless alternative for event-driven workloads +- [StaticWebApp](./static-web-app) - Static site hosting alternative + +## Official Documentation + +- [Azure App Service Overview](https://docs.microsoft.com/azure/app-service/overview) +- [Configure Runtime](https://docs.microsoft.com/azure/app-service/configure-language-nodejs) +- [Configure App Settings](https://docs.microsoft.com/azure/app-service/configure-common) +- [Deployment Best Practices](https://docs.microsoft.com/azure/app-service/deploy-best-practices) +- [Scaling](https://docs.microsoft.com/azure/app-service/manage-scale-up) diff --git a/alchemy-web/src/content/docs/providers/azure/blob-container.md b/alchemy-web/src/content/docs/providers/azure/blob-container.md new file mode 100644 index 000000000..0b79b92d1 --- /dev/null +++ b/alchemy-web/src/content/docs/providers/azure/blob-container.md @@ -0,0 +1,318 @@ +--- +title: BlobContainer +description: Azure Blob Container - object storage container for blobs +--- + +# BlobContainer + +A Blob Container provides a grouping of blobs within a Storage Account. Containers are similar to directories or folders in a file system. This is equivalent to AWS S3 Buckets and Cloudflare R2 Buckets. + +Key features: +- Organize blobs into logical groups +- Set public access levels (private, blob-level, container-level) +- Store unlimited blobs (up to 500 TB per storage account) +- Metadata support for container-level information +- Immutability policies for compliance (WORM storage) +- Soft delete for accidental deletion protection + +## Properties + +### Input Properties + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| `name` | `string` | No | Name of the blob container. Must be 3-63 characters, lowercase letters, numbers, and hyphens only. Cannot start or end with a hyphen, and cannot have consecutive hyphens. Defaults to `${app}-${stage}-${id}` (lowercase, valid characters only) | +| `storageAccount` | `string \| StorageAccount` | Yes | The storage account to create this container in | +| `resourceGroup` | `string` | No | The resource group containing the storage account. Required if `storageAccount` is a string name. Inherited from StorageAccount object if provided | +| `publicAccess` | `string` | No | Public access level. Options: `None` (no anonymous access), `Blob` (anonymous read for blobs), `Container` (anonymous read for container and blobs). Defaults to `None` | +| `metadata` | `Record` | No | Metadata key-value pairs for the container | +| `tags` | `Record` | No | Tags to apply to the blob container (stored in metadata) | +| `adopt` | `boolean` | No | Whether to adopt an existing blob container. Defaults to `false` | +| `delete` | `boolean` | No | Whether to delete the container when removed from Alchemy. **WARNING**: Deleting a container deletes ALL blobs inside it. Defaults to `true` | + +### Output Properties + +All input properties plus: + +| Property | Type | Description | +|----------|------|-------------| +| `id` | `string` | The Alchemy resource ID | +| `url` | `string` | The container URL (e.g., `https://{accountName}.blob.core.windows.net/{containerName}`) | +| `hasImmutabilityPolicy` | `boolean` | Whether the container has an immutability policy | +| `hasLegalHold` | `boolean` | Whether the container has a legal hold | +| `type` | `"azure::BlobContainer"` | Resource type identifier | + +## Usage + +### Basic Blob Container + +Create a private blob container: + +```typescript +import { alchemy } from "alchemy"; +import { ResourceGroup, StorageAccount, BlobContainer } from "alchemy/azure"; + +const app = await alchemy("my-app", { + azure: { + subscriptionId: process.env.AZURE_SUBSCRIPTION_ID! + } +}); + +const rg = await ResourceGroup("main", { + location: "eastus" +}); + +const storage = await StorageAccount("storage", { + resourceGroup: rg, + sku: "Standard_LRS" +}); + +const container = await BlobContainer("uploads", { + storageAccount: storage, + publicAccess: "None" // Private container +}); + +console.log(`Container URL: ${container.url}`); + +await app.finalize(); +``` + +### Public Blob Container + +Create a container with public read access for static assets: + +```typescript +const publicContainer = await BlobContainer("assets", { + storageAccount: storage, + publicAccess: "Blob", // Anonymous read access to blobs + metadata: { + purpose: "static-assets", + cdn: "enabled" + } +}); + +// Blobs in this container can be accessed via: +// https://{accountName}.blob.core.windows.net/assets/{blobName} +``` + +### Multiple Containers with Different Access Levels + +Create different containers for different purposes: + +```typescript +// Private container for user data +const privateData = await BlobContainer("user-data", { + storageAccount: storage, + publicAccess: "None", + metadata: { purpose: "user-storage" } +}); + +// Public container for images +const images = await BlobContainer("images", { + storageAccount: storage, + publicAccess: "Blob", + metadata: { purpose: "public-images" } +}); + +// Container-level public access for entire container listings +const downloads = await BlobContainer("downloads", { + storageAccount: storage, + publicAccess: "Container", // Can list all blobs anonymously + metadata: { purpose: "public-downloads" } +}); +``` + +### Container with Storage Account Reference + +Reference an existing storage account by name: + +```typescript +const container = await BlobContainer("backups", { + storageAccount: "myexistingstorage123", + resourceGroup: "my-resource-group", + publicAccess: "None", + metadata: { + purpose: "database-backups", + retention: "30-days" + } +}); +``` + +### Uploading Blobs + +Use the Azure Storage SDK to upload files to the container: + +```typescript +import { BlobServiceClient } from "@azure/storage-blob"; +import { Secret } from "alchemy"; + +const container = await BlobContainer("uploads", { + storageAccount: storage, + publicAccess: "None" +}); + +// Get connection string from storage account +const connectionString = Secret.unwrap(storage.primaryConnectionString); + +// Create blob service client +const blobService = BlobServiceClient.fromConnectionString(connectionString); +const containerClient = blobService.getContainerClient(container.name); + +// Upload a file +const blobName = "example.txt"; +const blockBlobClient = containerClient.getBlockBlobClient(blobName); +await blockBlobClient.upload("Hello, Azure Blob Storage!", 25); + +console.log(`Uploaded to: ${container.url}/${blobName}`); +``` + +### Container with Metadata + +Add custom metadata to organize containers: + +```typescript +const container = await BlobContainer("analytics", { + storageAccount: storage, + publicAccess: "None", + metadata: { + department: "engineering", + project: "data-pipeline", + retention: "90-days", + compliance: "gdpr" + } +}); +``` + +### Preserving Containers + +Prevent accidental deletion by setting `delete: false`: + +```typescript +const preservedContainer = await BlobContainer("important-data", { + storageAccount: storage, + publicAccess: "None", + delete: false, // Container won't be deleted when removed from Alchemy + metadata: { + protected: "true", + reason: "contains-production-data" + } +}); +``` + +### Adopting an Existing Container + +Adopt an existing blob container to manage it with Alchemy: + +```typescript +const existingContainer = await BlobContainer("existing", { + name: "my-existing-container", + storageAccount: "myexistingstorage123", + resourceGroup: "my-resource-group", + adopt: true +}); +``` + +## Important Notes + +### Public Access Levels + +| Level | Description | Use Case | +|-------|-------------|----------| +| `None` | No anonymous access (default) | Private data, user uploads | +| `Blob` | Anonymous read access to individual blobs | Public static assets (images, CSS, JS) | +| `Container` | Anonymous read access to container and blobs | Public downloads, file sharing | + +### Naming Constraints + +- Container names must be 3-63 characters +- Can only contain lowercase letters, numbers, and hyphens +- Cannot start or end with a hyphen +- Cannot have consecutive hyphens +- Must be unique within the storage account + +### Container URLs + +Containers are accessed via: +``` +https://{storageAccountName}.blob.core.windows.net/{containerName} +``` + +Individual blobs are accessed via: +``` +https://{storageAccountName}.blob.core.windows.net/{containerName}/{blobName} +``` + +### Blob Storage Features + +- **Block blobs**: Optimized for text and binary data (up to 4.75 TB per blob) +- **Page blobs**: Optimized for random read/write operations (VHD files) +- **Append blobs**: Optimized for append operations (logs) + +### Best Practices + +1. **Use private containers by default** - Only enable public access when necessary +2. **Organize by purpose** - Create separate containers for different data types +3. **Use metadata** - Tag containers with organizational information +4. **Enable soft delete** - Protect against accidental deletion (enabled at storage account level) +5. **Implement lifecycle policies** - Automatically tier or delete old data + +## Common Patterns + +### Static Website Hosting + +```typescript +const website = await BlobContainer("$web", { + storageAccount: storage, + publicAccess: "Blob" +}); + +// Enable static website hosting on the storage account +// (requires additional Azure SDK configuration) +``` + +### Backup Storage + +```typescript +const backups = await BlobContainer("backups", { + storageAccount: storage, + publicAccess: "None", + delete: false, // Preserve backups + metadata: { + purpose: "database-backups", + retention: "30-days", + schedule: "daily" + } +}); +``` + +### Multi-Environment Setup + +```typescript +const environments = ["dev", "staging", "production"]; + +const containers = await Promise.all( + environments.map(env => + BlobContainer(`${env}-data`, { + storageAccount: storage, + publicAccess: "None", + metadata: { + environment: env, + purpose: "application-data" + } + }) + ) +); +``` + +## Related Resources + +- [StorageAccount](./storage-account.md) - Required parent resource for blob containers +- [ResourceGroup](./resource-group.md) - Container for Azure resources +- [UserAssignedIdentity](./user-assigned-identity.md) - For managed identity access to blobs + +## Official Documentation + +- [Azure Blob Storage Overview](https://learn.microsoft.com/en-us/azure/storage/blobs/storage-blobs-overview) +- [Container Public Access](https://learn.microsoft.com/en-us/azure/storage/blobs/anonymous-read-access-configure) +- [Blob Storage SDK](https://learn.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-nodejs) +- [Immutability Policies](https://learn.microsoft.com/en-us/azure/storage/blobs/immutable-storage-overview) diff --git a/alchemy-web/src/content/docs/providers/azure/blob-state-store.md b/alchemy-web/src/content/docs/providers/azure/blob-state-store.md new file mode 100644 index 000000000..a04870c1e --- /dev/null +++ b/alchemy-web/src/content/docs/providers/azure/blob-state-store.md @@ -0,0 +1,239 @@ +--- +title: BlobStateStore +description: Learn how to use BlobStateStore for reliable, cloud-based state storage in your Alchemy applications using Azure Blob Storage. +--- + +The BlobStateStore provides reliable, scalable state storage for Alchemy applications using [Azure Blob Storage](https://learn.microsoft.com/en-us/azure/storage/blobs/). It's designed for cloud-based deployments where you need durable, shared state storage across multiple environments or team members. + +## Basic Usage + +Configure Alchemy to use Azure Blob Storage for state storage: + +```ts +import { BlobStateStore } from "alchemy/azure"; + +const app = await alchemy("my-app", { + stage: "prod", + phase: process.argv.includes("--destroy") ? "destroy" : "up", + stateStore: (scope) => new BlobStateStore(scope, { + accountName: "myalchemystate", + accountKey: process.env.AZURE_STORAGE_KEY, + containerName: "alchemy-state" + }) +}); +``` + +## Configuration Options + +### Account Name and Key + +Specify the Azure Storage account credentials: + +```ts +import { BlobStateStore } from "alchemy/azure"; + +const app = await alchemy("my-app", { + stateStore: (scope) => new BlobStateStore(scope, { + accountName: "mycompanyalchemy", + accountKey: process.env.AZURE_STORAGE_KEY, + containerName: "alchemy-state" + }) +}); +``` + +### Custom Container and Prefix + +Use a custom container name and prefix to organize state files: + +```ts +import { BlobStateStore } from "alchemy/azure"; + +const app = await alchemy("my-app", { + stateStore: (scope) => new BlobStateStore(scope, { + accountName: "sharedalchemy", + accountKey: process.env.AZURE_STORAGE_KEY, + containerName: "team-state", + prefix: "my-team/my-app/" + }) +}); +``` + +## Prerequisites + +### Create Storage Account and Container + +The Azure Storage account and container must exist before using BlobStateStore. + +```bash +# Create a storage account with Azure CLI +az storage account create \ + --name myalchemystate \ + --resource-group my-resource-group \ + --location eastus \ + --sku Standard_LRS + +# Get the storage account key +ACCOUNT_KEY=$(az storage account keys list \ + --account-name myalchemystate \ + --resource-group my-resource-group \ + --query "[0].value" \ + --output tsv) + +# Create the container +az storage container create \ + --name alchemy-state \ + --account-name myalchemystate \ + --account-key "$ACCOUNT_KEY" +``` + +### Configure Credentials + +Set Azure Storage credentials via environment variables: + +```bash +export AZURE_STORAGE_ACCOUNT=myalchemystate +export AZURE_STORAGE_KEY=your-account-key-here +``` + +Or provide them directly in the BlobStateStore configuration: + +```ts +const app = await alchemy("my-app", { + stateStore: (scope) => new BlobStateStore(scope, { + accountName: "myalchemystate", + accountKey: "your-account-key-here" + }) +}); +``` + +## State Organization + +BlobStateStore organizes state files using scope-based prefixes: + +``` +alchemy-state (container) + alchemy/my-app/dev/ + my-resource + my-other-resource + alchemy/my-app/prod/ + my-resource + my-other-resource +``` + +Keys containing forward slashes are converted to colons for consistency: + +- Resource key: `api/database/connection` +- Blob name: `alchemy/my-app/dev/api:database:connection` + +## Environment-Specific Configuration + +### Development + +Use environment-specific containers or prefixes: + +```ts +const isDev = process.env.NODE_ENV === "development"; + +const app = await alchemy("my-app", { + stage: isDev ? "dev" : "prod", + stateStore: (scope) => new BlobStateStore(scope, { + accountName: process.env.AZURE_STORAGE_ACCOUNT!, + accountKey: process.env.AZURE_STORAGE_KEY!, + containerName: isDev ? "alchemy-dev-state" : "alchemy-prod-state" + }) +}); +``` + +### Team Environments + +Share state across team members with appropriate access controls: + +```ts +const app = await alchemy("my-app", { + stage: "shared", + stateStore: (scope) => new BlobStateStore(scope, { + accountName: "teamsharedalchemy", + accountKey: process.env.AZURE_STORAGE_KEY!, + containerName: "team-state", + prefix: `${process.env.USER || "unknown"}/` + }) +}); +``` + +## GitHub Actions Integration + +Use BlobStateStore in CI/CD pipelines: + +```yaml +name: Deploy to Azure + +on: + push: + branches: [main] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: oven-sh/setup-bun@v1 + + - run: bun install + + - name: Deploy Infrastructure + run: bun run deploy + env: + AZURE_STORAGE_ACCOUNT: ${{ secrets.AZURE_STORAGE_ACCOUNT }} + AZURE_STORAGE_KEY: ${{ secrets.AZURE_STORAGE_KEY }} + ALCHEMY_PASSWORD: ${{ secrets.ALCHEMY_PASSWORD }} +``` + +## Error Handling + +BlobStateStore includes built-in error handling: + +- **Missing container**: Clear error message if container doesn't exist +- **Missing account credentials**: Clear error message with setup instructions +- **Missing blobs**: Gracefully handles missing state files +- **Network errors**: Proper error propagation with detailed messages +- **Permissions**: Clear Azure permission error messages + +## Performance Considerations + +- **Strong consistency**: Azure Blob Storage provides strong consistency for state operations +- **Batch operations**: Uses concurrent requests for multi-key operations +- **Network latency**: Consider storage account region proximity to your deployment location +- **Costs**: Azure Blob Storage charges for requests and storage (typically very low for state files) +- **Redundancy**: Supports LRS, GRS, and other redundancy options via storage account configuration + +## Security Best Practices + +### Use Shared Access Signatures (SAS) + +For production environments, consider using SAS tokens instead of account keys: + +```ts +// Note: BlobStateStore currently requires account keys +// SAS token support may be added in future versions +``` + +### Rotate Keys Regularly + +Azure Storage accounts support dual keys for zero-downtime rotation: + +1. Generate new secondary key +2. Update applications to use secondary key +3. Regenerate primary key +4. Update applications back to primary key + +### Network Security + +Configure firewall rules to restrict access to your storage account: + +```bash +# Allow only specific IP ranges +az storage account network-rule add \ + --account-name myalchemystate \ + --ip-address "203.0.113.0/24" +``` diff --git a/alchemy-web/src/content/docs/providers/azure/cdn-endpoint.md b/alchemy-web/src/content/docs/providers/azure/cdn-endpoint.md new file mode 100644 index 000000000..5ab4756a5 --- /dev/null +++ b/alchemy-web/src/content/docs/providers/azure/cdn-endpoint.md @@ -0,0 +1,417 @@ +--- +title: CDNEndpoint +description: Azure CDN Endpoint - Content delivery endpoint with caching and optimization +--- + +# CDNEndpoint + +A CDN Endpoint is the actual delivery point for cached content. It pulls content from origin servers and caches it at edge locations worldwide. Each endpoint has a unique hostname and can serve content from one or more origins. + +**AWS Equivalent**: CloudFront Distribution (behavior/origin configuration) + +Key features: +- Global edge caching for fast content delivery +- Custom domains with HTTPS support +- Origin configuration with health probes +- Caching rules and query string handling +- Content compression and optimization +- Purge and pre-load content +- Real-time metrics and logging + +## Properties + +### Input Properties + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| `name` | `string` | No | Name of the CDN endpoint. Must be globally unique (forms part of hostname). Must be 1-50 characters, lowercase alphanumeric and hyphens. Defaults to `${app}-${stage}-${id}` (lowercase) | +| `profile` | `string \| CDNProfile` | Yes | The CDN profile to create this endpoint in | +| `resourceGroup` | `string` | No | The resource group containing the CDN profile (required when using string profile reference) | +| `location` | `string` | No | Azure region for the endpoint. Defaults to profile's location | +| `origins` | `Array` | Yes | Origin servers to pull content from. At least one origin required. See origin configuration below | +| `isHttpAllowed` | `boolean` | No | Whether HTTP is allowed. Defaults to `true` | +| `isHttpsAllowed` | `boolean` | No | Whether HTTPS is allowed. Defaults to `true` | +| `queryStringCachingBehavior` | `string` | No | How to handle query strings for caching. See caching behavior below. Defaults to `IgnoreQueryString` | +| `optimizationType` | `string` | No | Optimization type for content delivery. See optimization types below. Defaults to `GeneralWebDelivery` | +| `contentTypesToCompress` | `string[]` | No | Content types to compress. Defaults to common web content types | +| `isCompressionEnabled` | `boolean` | No | Whether compression is enabled. Defaults to `true` | +| `tags` | `Record` | No | Tags to apply to the CDN endpoint | +| `adopt` | `boolean` | No | Whether to adopt an existing CDN endpoint. Defaults to `false` | +| `delete` | `boolean` | No | Whether to delete the CDN endpoint when removed from Alchemy. Defaults to `true` | + +### Origin Configuration + +Each origin in the `origins` array has these properties: + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| `name` | `string` | Yes | Name of the origin | +| `hostName` | `string` | Yes | Hostname of the origin server (e.g., `myapp.azurewebsites.net`) | +| `httpPort` | `number` | No | HTTP port for origin connections. Defaults to `80` | +| `httpsPort` | `number` | No | HTTPS port for origin connections. Defaults to `443` | +| `originHostHeader` | `string` | No | Host header to send to origin. Defaults to same as `hostName` | +| `priority` | `number` | No | Priority for this origin (used in origin groups). Defaults to `1` | +| `weight` | `number` | No | Weight for load balancing. Defaults to `1000` | + +### Output Properties + +All input properties plus: + +| Property | Type | Description | +|----------|------|-------------| +| `id` | `string` | The Alchemy resource ID | +| `cdnEndpointId` | `string` | The Azure resource ID | +| `hostName` | `string` | The CDN endpoint hostname (e.g., `{name}.azureedge.net`) | +| `provisioningState` | `string` | The provisioning state of the endpoint | +| `resourceState` | `string` | Resource state (Running, Creating, Deleting, Stopped) | +| `type` | `"azure::CDNEndpoint"` | Resource type identifier | + +## Query String Caching Behavior + +| Behavior | Description | When to Use | +|----------|-------------|-------------| +| `NotSet` | Default behavior (varies by SKU) | Let CDN decide | +| `IgnoreQueryString` | Query strings ignored for caching (default) | Static content, same content for all query strings | +| `BypassCaching` | Bypass cache for URLs with query strings | Dynamic content, personalized responses | +| `UseQueryString` | Cache every unique URL with query strings | Vary content by query parameters | + +## Optimization Types + +| Type | Description | Use Cases | +|------|-------------|-----------| +| `GeneralWebDelivery` | Standard web content (default) | Websites, APIs, static files | +| `GeneralMediaStreaming` | Video and audio streaming | Live streaming, media playback | +| `VideoOnDemandMediaStreaming` | On-demand video | Video libraries, courses | +| `LargeFileDownload` | Large file downloads (> 10 MB) | Software downloads, ISOs | +| `DynamicSiteAcceleration` | Dynamic content acceleration | APIs, dynamic web apps | + +## Usage + +### Basic CDN Endpoint for Static Website + +Serve static content from Blob Storage: + +```typescript +import { alchemy } from "alchemy"; +import { ResourceGroup, StorageAccount, CDNProfile, CDNEndpoint } from "alchemy/azure"; + +const app = await alchemy("my-app", { + azure: { + subscriptionId: process.env.AZURE_SUBSCRIPTION_ID! + } +}); + +const rg = await ResourceGroup("site", { + location: "eastus" +}); + +const storage = await StorageAccount("sitestorage", { + resourceGroup: rg, + sku: "Standard_LRS" +}); + +const cdn = await CDNProfile("site-cdn", { + resourceGroup: rg, + sku: "Standard_Microsoft" +}); + +const endpoint = await CDNEndpoint("site", { + profile: cdn, + origins: [{ + name: "storage-origin", + hostName: storage.primaryBlobEndpoint.replace("https://", "").replace("/", "") + }], + isHttpAllowed: false, // HTTPS only + isCompressionEnabled: true +}); + +console.log(`CDN URL: https://${endpoint.hostName}`); + +await app.finalize(); +``` + +### API CDN Endpoint with Caching + +Accelerate API responses with appropriate caching: + +```typescript +import { FunctionApp } from "alchemy/azure"; + +const api = await FunctionApp("api", { + resourceGroup: rg, + runtime: "node", + runtimeVersion: "20" +}); + +const cdnProfile = await CDNProfile("api-cdn", { + resourceGroup: rg, + sku: "Standard_AzureFrontDoor" +}); + +const apiEndpoint = await CDNEndpoint("api", { + profile: cdnProfile, + origins: [{ + name: "api-origin", + hostName: `${api.name}.azurewebsites.net`, + originHostHeader: `${api.name}.azurewebsites.net` + }], + queryStringCachingBehavior: "UseQueryString", // Cache by query params + optimizationType: "DynamicSiteAcceleration", + isCompressionEnabled: true, + contentTypesToCompress: [ + "application/json", + "application/javascript", + "text/css", + "text/html" + ] +}); +``` + +### Video Streaming CDN + +Optimize for video on demand: + +```typescript +const mediaEndpoint = await CDNEndpoint("media", { + profile: mediaCdn, + origins: [{ + name: "media-origin", + hostName: `${mediaStorage.name}.blob.core.windows.net` + }], + optimizationType: "VideoOnDemandMediaStreaming", + isCompressionEnabled: false, // Video already compressed + queryStringCachingBehavior: "IgnoreQueryString" +}); +``` + +### Multiple Origin with Failover + +Configure multiple origins for redundancy: + +```typescript +const resilientEndpoint = await CDNEndpoint("resilient", { + profile: cdn, + origins: [ + { + name: "primary", + hostName: "primary.example.com", + priority: 1, // Higher priority (lower number) + weight: 1000 + }, + { + name: "backup", + hostName: "backup.example.com", + priority: 2, // Lower priority + weight: 1000 + } + ] +}); + +// CDN will try primary first, failover to backup if primary is down +``` + +### Large File Downloads + +Optimize for large file distribution: + +```typescript +const downloadsEndpoint = await CDNEndpoint("downloads", { + profile: cdn, + origins: [{ + name: "files", + hostName: `${storage.name}.blob.core.windows.net` + }], + optimizationType: "LargeFileDownload", // Optimized for files > 10 MB + isCompressionEnabled: false, // Don't compress already compressed files + queryStringCachingBehavior: "IgnoreQueryString" +}); +``` + +### HTTPS-Only Endpoint + +Force HTTPS for all content: + +```typescript +const secureEndpoint = await CDNEndpoint("secure", { + profile: cdn, + origins: [{ + name: "app", + hostName: "app.azurewebsites.net" + }], + isHttpAllowed: false, // Disable HTTP + isHttpsAllowed: true +}); +``` + +### Adopt Existing CDN Endpoint + +Adopt an existing endpoint: + +```typescript +const endpoint = await CDNEndpoint("legacy", { + name: "existing-endpoint-name", + profile: "existing-profile-name", + resourceGroup: rg, + origins: [{ + name: "origin", + hostName: "origin.example.com" + }], + adopt: true // Adopt the existing endpoint +}); +``` + +## Common Patterns + +### Static Website Hosting + +Full stack for static website with CDN: + +```typescript +// 1. Storage account for static files +const storage = await StorageAccount("site", { + resourceGroup: rg, + sku: "Standard_LRS" +}); + +// 2. CDN profile +const cdn = await CDNProfile("site-cdn", { + resourceGroup: rg, + sku: "Standard_Microsoft" +}); + +// 3. CDN endpoint +const endpoint = await CDNEndpoint("site", { + profile: cdn, + origins: [{ + name: "storage", + hostName: storage.primaryBlobEndpoint.replace("https://", "").replace("/", "") + }], + isHttpAllowed: false, + isCompressionEnabled: true +}); + +// 4. Configure custom domain (manual DNS step) +console.log(`Add CNAME: www -> ${endpoint.hostName}`); +``` + +### Microservices API Gateway + +CDN as API gateway with caching: + +```typescript +const gatewayEndpoint = await CDNEndpoint("gateway", { + profile: cdn, + origins: [ + { + name: "users-api", + hostName: "users-api.azurewebsites.net", + priority: 1 + }, + { + name: "products-api", + hostName: "products-api.azurewebsites.net", + priority: 1 + } + ], + queryStringCachingBehavior: "UseQueryString", + optimizationType: "DynamicSiteAcceleration" +}); + +// Use URL routing or custom domains to route to different origins +``` + +### Global Content Distribution + +Multi-region content delivery: + +```typescript +const globalEndpoint = await CDNEndpoint("global", { + profile: cdn, + origins: [ + { + name: "us-east", + hostName: "useast.blob.core.windows.net", + priority: 1, + weight: 2000 // Higher weight = more traffic + }, + { + name: "europe", + hostName: "europe.blob.core.windows.net", + priority: 1, + weight: 1000 + } + ] +}); + +// CDN automatically routes to closest origin +``` + +## Important Notes + +### Global Uniqueness + +Endpoint names must be globally unique across all of Azure because they form the hostname: +- Endpoint URL: `https://{name}.azureedge.net` +- Choose unique names to avoid conflicts + +### Immutable Properties + +These properties cannot be changed after creation (requires replacement): +- `name` - The endpoint name (impacts hostname) +- Origin `hostName` - Cannot change origin hostname (can add/remove origins) + +### Caching Duration + +Default caching behavior: +- No cache headers from origin: 7 days default +- Set `Cache-Control` headers on origin for precise control +- Use caching rules in CDN for override + +### Content Purge + +To purge cached content: +- Use Azure Portal or Azure CLI +- Purge by path pattern +- Can take up to 10 minutes to propagate globally + +### Custom Domains + +To use custom domains: +1. Create CNAME record: `www.example.com` → `{endpoint}.azureedge.net` +2. Add custom domain in Azure Portal +3. Enable HTTPS with managed certificate (free) + +### HTTPS Certificates + +- **Managed certificates**: Free, auto-renewed, supports wildcard +- **Custom certificates**: Bring your own certificate +- **HTTP to HTTPS redirect**: Configure in CDN rules + +### Performance Tips + +1. **Enable compression**: Reduces bandwidth and improves load times +2. **Optimize content types**: Choose correct optimization type for your content +3. **Query string caching**: Match your caching needs +4. **Origin configuration**: Set appropriate host headers +5. **Multiple origins**: Use for redundancy and load balancing + +### Cost Optimization + +1. **Caching**: Longer cache times reduce origin requests +2. **Compression**: Reduces data transfer costs +3. **Right-size SKU**: Premium features cost more +4. **Monitor usage**: Track data transfer and requests + +## Related Resources + +- [CDNProfile](./cdn-profile.md) - Required parent CDN profile +- [StorageAccount](./storage-account.md) - Common origin for static content +- [FunctionApp](./function-app.md) - Common origin for APIs +- [StaticWebApp](./static-web-app.md) - Alternative for static sites + +## Official Documentation + +- [CDN Endpoints Documentation](https://docs.microsoft.com/azure/cdn/cdn-create-endpoint-how-to) +- [CDN Caching Rules](https://docs.microsoft.com/azure/cdn/cdn-caching-rules) +- [CDN Custom Domains](https://docs.microsoft.com/azure/cdn/cdn-map-content-to-custom-domain) +- [CDN Performance Tips](https://docs.microsoft.com/azure/cdn/cdn-optimization-overview) diff --git a/alchemy-web/src/content/docs/providers/azure/cdn-profile.md b/alchemy-web/src/content/docs/providers/azure/cdn-profile.md new file mode 100644 index 000000000..180d4ee8a --- /dev/null +++ b/alchemy-web/src/content/docs/providers/azure/cdn-profile.md @@ -0,0 +1,280 @@ +--- +title: CDNProfile +description: Azure CDN Profile - Content Delivery Network profile container for endpoints +--- + +# CDNProfile + +An Azure CDN Profile is a container for CDN endpoints. All endpoints in a profile share the same pricing tier (SKU) and configuration. Use CDN to accelerate content delivery globally by caching content at edge locations close to end users. + +**AWS Equivalent**: CloudFront Distribution (container) +**Cloudflare Equivalent**: Zone with CDN enabled + +Key features: +- Global content delivery with edge caching +- Multiple CDN providers (Microsoft, Akamai, Verizon, Azure Front Door) +- Custom domain support with HTTPS +- Caching rules and query string handling +- Compression and optimization for different content types +- Real-time analytics and monitoring +- DDoS protection and WAF (Premium tiers) + +## Properties + +### Input Properties + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| `name` | `string` | No | Name of the CDN profile. Must be 1-260 characters, alphanumeric and hyphens only. Defaults to `${app}-${stage}-${id}` | +| `resourceGroup` | `string \| ResourceGroup` | Yes | The resource group to create this CDN profile in | +| `location` | `string` | No | Azure region for the CDN profile. Defaults to the resource group's location | +| `sku` | `string` | No | The pricing tier (CDN provider). See SKU comparison below. Defaults to `Standard_Microsoft` | +| `tags` | `Record` | No | Tags to apply to the CDN profile | +| `adopt` | `boolean` | No | Whether to adopt an existing CDN profile. Defaults to `false` | +| `delete` | `boolean` | No | Whether to delete the CDN profile when removed from Alchemy. **WARNING**: Deleting a profile deletes ALL endpoints. Defaults to `true` | + +### Output Properties + +All input properties plus: + +| Property | Type | Description | +|----------|------|-------------| +| `id` | `string` | The Alchemy resource ID | +| `cdnProfileId` | `string` | The Azure resource ID | +| `provisioningState` | `string` | The provisioning state of the profile | +| `resourceState` | `string` | Resource state (Active, Creating, Deleting, Disabled) | +| `type` | `"azure::CDNProfile"` | Resource type identifier | + +## SKU Comparison + +| SKU | Provider | Performance | Features | Use Cases | +|-----|----------|-------------|----------|-----------| +| `Standard_Microsoft` | Microsoft | Good | Azure-native, simple | General web content, APIs | +| `Standard_Akamai` | Akamai | Excellent | Global reach, fast | High-traffic websites | +| `Standard_Verizon` | Verizon | Very good | Advanced features | Enterprise applications | +| `Premium_Verizon` | Verizon | Very good | Rules engine, analytics | Complex routing, customization | +| `Standard_AzureFrontDoor` | Microsoft | Excellent | Modern, L7 load balancing | **Recommended for new apps** | +| `Premium_AzureFrontDoor` | Microsoft | Excellent | WAF, Private Link | Secure, high-performance apps | + +**Recommendation**: Use `Standard_AzureFrontDoor` for new applications. It provides the best combination of performance, features, and modern architecture. + +## Usage + +### Basic CDN Profile with Microsoft CDN + +Create a CDN profile for content acceleration: + +```typescript +import { alchemy } from "alchemy"; +import { ResourceGroup, CDNProfile } from "alchemy/azure"; + +const app = await alchemy("my-app", { + azure: { + subscriptionId: process.env.AZURE_SUBSCRIPTION_ID! + } +}); + +const rg = await ResourceGroup("cdn", { + location: "eastus" +}); + +const cdn = await CDNProfile("content-cdn", { + resourceGroup: rg, + sku: "Standard_Microsoft" // Azure-native CDN +}); + +console.log(`CDN Profile: ${cdn.name}`); +console.log(`State: ${cdn.resourceState}`); + +await app.finalize(); +``` + +### Azure Front Door Standard Profile (Recommended) + +Create a modern CDN profile with Azure Front Door: + +```typescript +const cdn = await CDNProfile("frontdoor", { + resourceGroup: rg, + sku: "Standard_AzureFrontDoor", // Modern, recommended + tags: { + environment: "production", + purpose: "cdn" + } +}); + +// Create endpoints using CDNEndpoint resource +``` + +### Premium Verizon with Advanced Features + +Create a premium CDN profile with rules engine and analytics: + +```typescript +const cdn = await CDNProfile("premium-cdn", { + resourceGroup: rg, + sku: "Premium_Verizon" // Advanced features, rules engine +}); + +// Access to: +// - Rules engine for complex routing +// - Real-time analytics +// - Token authentication +// - Advanced caching controls +``` + +### Multi-Region CDN Setup + +Create CDN profiles in multiple regions for redundancy: + +```typescript +const primaryCdn = await CDNProfile("primary-cdn", { + resourceGroup: primaryRg, + sku: "Standard_AzureFrontDoor" +}); + +const secondaryCdn = await CDNProfile("secondary-cdn", { + resourceGroup: secondaryRg, + sku: "Standard_AzureFrontDoor" +}); + +// Configure DNS-based failover between profiles +``` + +### Adopt Existing CDN Profile + +Adopt an existing CDN profile: + +```typescript +const cdn = await CDNProfile("legacy-cdn", { + name: "existing-cdn-profile", + resourceGroup: rg, + sku: "Standard_Microsoft", + adopt: true // Adopt the existing profile +}); +``` + +## Common Patterns + +### Static Website CDN + +Serve a static website through CDN: + +```typescript +import { StorageAccount, CDNProfile, CDNEndpoint } from "alchemy/azure"; + +// Create storage for static site +const storage = await StorageAccount("site-storage", { + resourceGroup: rg, + sku: "Standard_LRS" +}); + +// Create CDN profile +const cdn = await CDNProfile("site-cdn", { + resourceGroup: rg, + sku: "Standard_Microsoft" +}); + +// Create CDN endpoint pointing to blob storage +// (See CDNEndpoint documentation) +``` + +### API Acceleration + +Accelerate API responses with CDN caching: + +```typescript +import { FunctionApp } from "alchemy/azure"; + +const api = await FunctionApp("api", { + resourceGroup: rg, + runtime: "node", + runtimeVersion: "20" +}); + +const cdn = await CDNProfile("api-cdn", { + resourceGroup: rg, + sku: "Standard_AzureFrontDoor" // L7 load balancing +}); + +// Configure CDN endpoint with caching rules for API +``` + +### Media Streaming + +Optimize for video streaming: + +```typescript +const mediaCdn = await CDNProfile("media-cdn", { + resourceGroup: rg, + sku: "Premium_Verizon" // Best for media +}); + +// Configure endpoints with: +// - VideoOnDemandMediaStreaming optimization +// - Origin streaming protocols +// - Token authentication for content protection +``` + +## Important Notes + +### SKU Selection + +Choose your SKU carefully: +- **Standard_Microsoft**: Best for general-purpose web content, Azure-integrated +- **Standard_Akamai**: Best global performance, may cost more +- **Standard_Verizon**: Good balance of features and performance +- **Premium_Verizon**: For complex scenarios requiring rules engine +- **Azure Front Door**: Modern choice, recommended for new applications +- **Premium Front Door**: Adds WAF, Private Link, advanced security + +### Immutable Properties + +These properties cannot be changed after creation (requires replacement): +- `name` - The profile name +- `sku` - The CDN provider (cannot switch between SKUs) + +### Endpoint Management + +CDN Profiles are containers for CDN Endpoints: +- One profile can have multiple endpoints +- All endpoints share the profile's SKU +- Deleting a profile deletes all endpoints +- Use separate profiles for different SKUs or billing separation + +### Global Resources + +CDN Profiles are global resources but require a location for deployment: +- Location determines where metadata is stored +- Content is cached globally at edge locations regardless of profile location +- Choose location close to your management location + +### Pricing + +CDN pricing varies by SKU: +- **Data transfer out**: Main cost component +- **Requests**: Charged per HTTP/HTTPS request +- **Premium features**: Additional costs for rules engine, WAF, etc. +- See [Azure CDN Pricing](https://azure.microsoft.com/pricing/details/cdn/) + +### Migration Between SKUs + +You cannot change the SKU of an existing profile: +- Create new profile with desired SKU +- Create endpoints in new profile +- Migrate traffic via DNS +- Delete old profile when migration is complete + +## Related Resources + +- [CDNEndpoint](./cdn-endpoint.md) - Create CDN endpoints within a profile +- [ResourceGroup](./resource-group.md) - Required parent resource +- [StorageAccount](./storage-account.md) - Common origin for static content +- [FunctionApp](./function-app.md) - Common origin for APIs + +## Official Documentation + +- [Azure CDN Documentation](https://docs.microsoft.com/azure/cdn/) +- [Azure Front Door Documentation](https://docs.microsoft.com/azure/frontdoor/) +- [CDN Features by SKU](https://docs.microsoft.com/azure/cdn/cdn-features) +- [CDN Pricing](https://azure.microsoft.com/pricing/details/cdn/) diff --git a/alchemy-web/src/content/docs/providers/azure/cognitive-services.md b/alchemy-web/src/content/docs/providers/azure/cognitive-services.md new file mode 100644 index 000000000..03ffda2c4 --- /dev/null +++ b/alchemy-web/src/content/docs/providers/azure/cognitive-services.md @@ -0,0 +1,381 @@ +--- +title: CognitiveServices +description: Azure Cognitive Services - AI and ML APIs for vision, speech, language, and decision making +--- + +# CognitiveServices + +Azure Cognitive Services provides AI capabilities through REST APIs and SDKs, including computer vision, speech recognition, natural language processing, decision making, and Azure OpenAI Service. + +**Unique to Azure**: While AWS has individual AI services (Rekognition, Comprehend, Polly), Azure Cognitive Services provides a unified platform with consistent authentication, billing, and developer experience. + +Key features: +- Vision APIs (image analysis, face detection, OCR) +- Speech APIs (speech-to-text, text-to-speech, translation) +- Language APIs (sentiment analysis, entity recognition, translation) +- Decision APIs (anomaly detection, content moderation, personalization) +- Azure OpenAI Service (GPT models, DALL-E, embeddings) +- Multi-service accounts for all APIs or single-service accounts +- Free tier available for development and testing + +## Properties + +### Input Properties + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| `name` | `string` | No | Name of the Cognitive Services account. Must be 2-64 characters, alphanumeric, hyphens, and underscores. Defaults to `${app}-${stage}-${id}` | +| `resourceGroup` | `string \| ResourceGroup` | Yes | The resource group to create this Cognitive Services account in | +| `location` | `string` | No | Azure region for the Cognitive Services account. Defaults to the resource group's location | +| `kind` | `string` | No | The kind of Cognitive Services API to create. See API kinds below. Defaults to `CognitiveServices` (multi-service) | +| `sku` | `string` | No | The pricing tier. Options: `F0` (free), `S0`-`S10` (standard tiers). Defaults to `S0`. See SKU details below | +| `publicNetworkAccess` | `boolean` | No | Enable public network access. Defaults to `true` | +| `customSubDomain` | `string` | No | Custom subdomain for the endpoint. Required for some features like custom domains and AAD authentication | +| `networkAcls` | `object` | No | Network ACLs to restrict access. See network restrictions below | +| `tags` | `Record` | No | Tags to apply to the Cognitive Services account | +| `adopt` | `boolean` | No | Whether to adopt an existing Cognitive Services account. Defaults to `false` | +| `delete` | `boolean` | No | Whether to delete the Cognitive Services account when removed from Alchemy. Defaults to `true` | + +### Output Properties + +All input properties plus: + +| Property | Type | Description | +|----------|------|-------------| +| `id` | `string` | The Alchemy resource ID | +| `cognitiveServicesId` | `string` | The Azure resource ID | +| `endpoint` | `string` | The endpoint URL for the Cognitive Services API | +| `primaryKey` | `Secret` | Primary access key for API authentication | +| `secondaryKey` | `Secret` | Secondary access key for key rotation | +| `provisioningState` | `string` | The provisioning state of the account | +| `type` | `"azure::CognitiveServices"` | Resource type identifier | + +## API Kinds + +| Kind | Description | Use Cases | +|------|-------------|-----------| +| `CognitiveServices` | Multi-service account with access to all APIs | Development, prototyping, multiple AI features | +| `ComputerVision` | Image analysis and OCR | Object detection, image classification, text extraction | +| `Face` | Face detection and recognition | Identity verification, emotion detection | +| `TextAnalytics` | Sentiment analysis, key phrases, entity recognition | Customer feedback analysis, content categorization | +| `SpeechServices` | Speech-to-text, text-to-speech, translation | Voice assistants, transcription, accessibility | +| `LUIS` | Language Understanding for natural language processing | Chatbots, command parsing | +| `QnAMaker` | Question and answer service | FAQ bots, knowledge bases | +| `CustomVision.Training` | Custom image classification training | Product recognition, quality inspection | +| `CustomVision.Prediction` | Custom image classification prediction | Applying trained models | +| `AnomalyDetector` | Time-series anomaly detection | Monitoring, fraud detection | +| `ContentModerator` | Content moderation for text, images, videos | User-generated content filtering | +| `Personalizer` | Reinforcement learning for personalization | Content recommendations | +| `FormRecognizer` | Extract data from forms and documents | Invoice processing, form automation | +| `TranslatorText` | Text translation | Multi-language support | +| `OpenAI` | Azure OpenAI Service for GPT models | Chatbots, content generation, embeddings | + +## SKU Tiers + +| SKU | Description | Pricing | +|-----|-------------|---------| +| `F0` | Free tier with limited requests per month | Free (varies by service) | +| `S0` | Standard tier with pay-as-you-go pricing | Most common, flexible | +| `S1`-`S10` | Service-specific standard tiers | Varies by service | + +## Usage + +### Multi-Service Cognitive Services Account + +Create a single account with access to all Cognitive Services APIs: + +```typescript +import { alchemy } from "alchemy"; +import { ResourceGroup, CognitiveServices } from "alchemy/azure"; + +const app = await alchemy("my-app", { + azure: { + subscriptionId: process.env.AZURE_SUBSCRIPTION_ID! + } +}); + +const rg = await ResourceGroup("ai", { + location: "eastus" +}); + +const cognitive = await CognitiveServices("ai", { + resourceGroup: rg, + kind: "CognitiveServices", // All APIs + sku: "S0" // Standard tier +}); + +console.log(`Endpoint: ${cognitive.endpoint}`); +console.log(`Primary Key available as Secret`); + +await app.finalize(); +``` + +### Computer Vision API + +Create a dedicated Computer Vision resource for image analysis: + +```typescript +const vision = await CognitiveServices("vision", { + resourceGroup: rg, + kind: "ComputerVision", + sku: "S1", + customSubDomain: "myapp-vision" // Required for some features +}); + +// Use in your application +console.log(`Vision Endpoint: ${vision.endpoint}`); +``` + +### Azure OpenAI Service + +Create an OpenAI resource for GPT models: + +```typescript +const openai = await CognitiveServices("openai", { + resourceGroup: rg, + kind: "OpenAI", + sku: "S0", + customSubDomain: "myapp-openai" // Required for OpenAI +}); + +// Deploy models using Azure OpenAI Studio or SDK +// Access via SDK with endpoint and key +``` + +### Text Analytics with Free Tier + +Use the free tier for development and testing: + +```typescript +const textAnalytics = await CognitiveServices("dev-text", { + resourceGroup: rg, + kind: "TextAnalytics", + sku: "F0" // Free tier +}); + +// Limited to 5,000 transactions per month +``` + +### Speech Services + +Create a Speech Services resource for voice applications: + +```typescript +const speech = await CognitiveServices("speech", { + resourceGroup: rg, + kind: "SpeechServices", + sku: "S0", + customSubDomain: "myapp-speech" +}); + +// Use for speech-to-text, text-to-speech, translation +``` + +### Cognitive Services with Network Restrictions + +Restrict access to specific IP addresses and virtual networks: + +```typescript +const secure = await CognitiveServices("secure-ai", { + resourceGroup: rg, + kind: "CognitiveServices", + networkAcls: { + defaultAction: "Deny", + ipRules: ["203.0.113.0/24"], // Your office IP range + virtualNetworkRules: [ + "/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Network/virtualNetworks/{vnet}/subnets/ai-subnet" + ] + } +}); +``` + +### Use with Function App + +Integrate Cognitive Services with a Function App: + +```typescript +import { FunctionApp } from "alchemy/azure"; + +const vision = await CognitiveServices("vision", { + resourceGroup: rg, + kind: "ComputerVision", + sku: "S1" +}); + +const api = await FunctionApp("image-api", { + resourceGroup: rg, + runtime: "node", + runtimeVersion: "20", + environmentVariables: { + VISION_ENDPOINT: vision.endpoint, + VISION_KEY: vision.primaryKey + } +}); +``` + +### Adopt Existing Cognitive Services + +Adopt an existing Cognitive Services account: + +```typescript +const cognitive = await CognitiveServices("legacy-ai", { + name: "existing-cognitive-account", + resourceGroup: rg, + kind: "CognitiveServices", + adopt: true // Adopt the existing account +}); +``` + +## Common Patterns + +### Image Analysis Pipeline + +Process uploaded images with Computer Vision: + +```typescript +const vision = await CognitiveServices("vision", { + resourceGroup: rg, + kind: "ComputerVision", + sku: "S1" +}); + +// 1. User uploads image to Blob Storage +// 2. Function App triggered by blob upload +// 3. Function calls Computer Vision API to analyze image +// 4. Results stored in database +``` + +### Sentiment Analysis for Customer Feedback + +Analyze customer feedback with Text Analytics: + +```typescript +const textAnalytics = await CognitiveServices("sentiment", { + resourceGroup: rg, + kind: "TextAnalytics", + sku: "S0" +}); + +// Analyze customer reviews, support tickets, social media posts +// Extract sentiment scores and key phrases +``` + +### Chatbot with LUIS and QnA Maker + +Build an intelligent chatbot: + +```typescript +const luis = await CognitiveServices("bot-luis", { + resourceGroup: rg, + kind: "LUIS", + sku: "S0" +}); + +const qna = await CognitiveServices("bot-qna", { + resourceGroup: rg, + kind: "QnAMaker", + sku: "S0" +}); + +// LUIS handles intent recognition +// QnA Maker provides answers to common questions +``` + +### Voice Assistant + +Create a voice-enabled application: + +```typescript +const speech = await CognitiveServices("assistant-speech", { + resourceGroup: rg, + kind: "SpeechServices", + sku: "S0" +}); + +// Speech-to-text for user input +// Text-to-speech for responses +// Intent recognition with LUIS +``` + +## Important Notes + +### Immutable Properties + +These properties cannot be changed after creation (requires replacement): +- `name` - The account name +- `location` - The Azure region +- `kind` - The API kind + +### Custom Subdomain + +- Required for Azure OpenAI Service +- Required for custom domain names +- Required for Azure AD authentication +- Becomes part of the endpoint: `https://{customSubDomain}.cognitiveservices.azure.com` + +### API Keys vs Azure AD + +- **API Keys**: Simpler, use `primaryKey` and `secondaryKey` +- **Azure AD**: More secure, use managed identities or service principals +- Set `disableLocalAuth: true` to require Azure AD only + +### Rate Limits + +Each SKU tier has different rate limits: +- **Free (F0)**: Limited transactions per month (varies by service) +- **Standard (S0-S10)**: Pay-per-transaction with higher limits +- Check specific service documentation for exact limits + +### Regional Availability + +Not all Cognitive Services are available in all regions: +- **Azure OpenAI**: Limited to specific regions (check availability) +- **Most services**: Available in major regions worldwide +- Check [Azure Products by Region](https://azure.microsoft.com/global-infrastructure/services/) + +### Pricing + +- **Free Tier**: Good for development, limited transactions +- **Standard Tier**: Pay-per-transaction model +- **Premium Features**: Some features (e.g., custom models) have additional costs +- See [Cognitive Services Pricing](https://azure.microsoft.com/pricing/details/cognitive-services/) + +### Security Best Practices + +1. Use custom subdomains for production workloads +2. Rotate API keys regularly (use `secondaryKey` for rotation) +3. Use Azure AD authentication when possible +4. Apply network restrictions for sensitive workloads +5. Store API keys as Secrets (automatically done by Alchemy) +6. Use managed identities for Azure resources + +### Multi-Service vs Single-Service + +**Multi-Service Account (`CognitiveServices`)**: +- āœ… Access to all APIs with one key +- āœ… Simplified billing +- āœ… Good for development and prototyping +- āŒ Less granular cost tracking +- āŒ Single security boundary + +**Single-Service Accounts**: +- āœ… Granular cost tracking per service +- āœ… Separate security boundaries +- āœ… Service-specific SKUs and features +- āŒ More resources to manage +- āŒ Multiple keys to rotate + +## Related Resources + +- [ResourceGroup](./resource-group.md) - Required parent resource +- [FunctionApp](./function-app.md) - Process AI workloads with serverless functions +- [StorageAccount](./storage-account.md) - Store images, documents for processing + +## Official Documentation + +- [Cognitive Services Documentation](https://docs.microsoft.com/azure/cognitive-services/) +- [Computer Vision](https://docs.microsoft.com/azure/cognitive-services/computer-vision/) +- [Speech Services](https://docs.microsoft.com/azure/cognitive-services/speech-service/) +- [Text Analytics](https://docs.microsoft.com/azure/cognitive-services/text-analytics/) +- [Azure OpenAI Service](https://docs.microsoft.com/azure/cognitive-services/openai/) +- [Cognitive Services Pricing](https://azure.microsoft.com/pricing/details/cognitive-services/) diff --git a/alchemy-web/src/content/docs/providers/azure/container-instance.md b/alchemy-web/src/content/docs/providers/azure/container-instance.md new file mode 100644 index 000000000..ce900ef54 --- /dev/null +++ b/alchemy-web/src/content/docs/providers/azure/container-instance.md @@ -0,0 +1,375 @@ +--- +title: ContainerInstance +description: Azure Container Instance for serverless Docker containers +--- + +# ContainerInstance + +Azure Container Instance provides serverless container hosting in Azure, equivalent to AWS ECS Fargate. They offer fast startup times (< 1 second), per-second billing, and support for public IPs, virtual network integration, and environment variables with secrets. + +## Properties + +### Input + +| Property | Type | Required | Default | Description | +|----------|------|----------|---------|-------------| +| `name` | `string` | No | `${app}-${stage}-${id}` | Container group name (1-63 chars, lowercase, alphanumeric, hyphens) | +| `resourceGroup` | `string \| ResourceGroup` | Yes | - | Resource group to create the container in | +| `location` | `string` | No | Inherited from resource group | Azure region | +| `image` | `string` | Yes | - | Docker image to run | +| `cpu` | `number` | No | `1` | Number of CPU cores (can be fractional: 0.5, 1.5) | +| `memoryInGB` | `number` | No | `1.5` | Memory in GB (can be fractional) | +| `osType` | `"Linux" \| "Windows"` | No | `"Linux"` | Operating system type | +| `restartPolicy` | `"Always" \| "OnFailure" \| "Never"` | No | `"Always"` | Container restart policy | +| `command` | `string[]` | No | - | Command to run (overrides ENTRYPOINT) | +| `environmentVariables` | `Record` | No | - | Environment variables | +| `ipAddress` | `ContainerIPAddress` | No | - | Public or private IP configuration | +| `subnet` | `SubnetConfig` | No | - | Deploy into VNet subnet | +| `tags` | `Record` | No | - | Resource tags | +| `adopt` | `boolean` | No | `false` | Adopt existing container instance | +| `delete` | `boolean` | No | `true` | Delete when removed from Alchemy | + +### IP Address Configuration + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| `type` | `"Public" \| "Private"` | Yes | IP address type | +| `ports` | `ContainerPort[]` | Yes | Ports to expose | +| `dnsNameLabel` | `string` | No | DNS label (creates `