diff --git a/README.md b/README.md
index fbab1f2e..bf8a3b95 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,6 @@
# LocalStack Docs v2
+
This is just the Starlight starter for now. We can begin moving existing content into `/content/docs`.
## Starlight Starter Kit: Basics
diff --git a/astro.config.mjs b/astro.config.mjs
index 80638542..1b91f906 100644
--- a/astro.config.mjs
+++ b/astro.config.mjs
@@ -629,10 +629,6 @@ export default defineConfig({
label: 'LocalStack Docker Extension',
slug: 'aws/configuration/localstack-docker-extension',
},
- {
- label: 'LocalSurf',
- slug: 'aws/configuration/localsurf',
- },
],
},
{
diff --git a/public/_redirects b/public/_redirects
index 2fcbfbf9..f626cd01 100644
--- a/public/_redirects
+++ b/public/_redirects
@@ -122,7 +122,7 @@
/references/coverage/coverage_es/ /aws/services/es 301
/user-guide/integrations/sdks/javascript/ /aws/connecting/aws-sdks/net/javascript 301
/references/coverage/coverage_iam/ /aws/services/iam 301
-/user-guide/tools/localsurf/ /aws/configuration/localsurf 301
+/user-guide/tools/localsurf/ /aws/customization/networking/ 301
/user-guide/extensions/extensions-library/ /aws/configuration/extensions/extensions-library 301
/tutorials/ecs-ecr-container-app/ /aws/tutorials/ecs-ecr-container-app 301
/references/coverage/coverage_elastictranscoder/ /aws/services/elastictranscoder 301
@@ -529,7 +529,7 @@
/references/coverage/coverage_es /aws/services/es 301
/user-guide/integrations/sdks/javascript /aws/connecting/aws-sdks/net/javascript 301
/references/coverage/coverage_iam /aws/services/iam 301
-/user-guide/tools/localsurf /aws/configuration/localsurf 301
+/user-guide/tools/localsurf /aws/customization/networking/ 301
/user-guide/extensions/extensions-library /aws/configuration/extensions/extensions-library 301
/tutorials/ecs-ecr-container-app /aws/tutorials/ecs-ecr-container-app 301
/references/coverage/coverage_elastictranscoder /aws/services/elastictranscoder 301
@@ -944,8 +944,8 @@
/aws/tooling/testing-utils/ /aws/configuration/testing-utils 301
/aws/tooling/localstack-docker-extension /aws/configuration/localstack-docker-extension 301
/aws/tooling/localstack-docker-extension/ /aws/configuration/localstack-docker-extension 301
-/aws/tooling/localsurf /aws/configuration/localsurf 301
-/aws/tooling/localsurf/ /aws/configuration/localsurf 301
+/aws/tooling/localsurf /aws/customization/networking/ 301
+/aws/tooling/localsurf/ /aws/customization/networking/ 301
/aws/tooling /aws/developer-tools 301
/aws/tooling/ /aws/developer-tools/ 301
@@ -999,8 +999,8 @@
/aws/capabilities/localstack-sdks/java-sdk/ /aws/configuration/localstack-sdks/java-sdk 301
/aws/capabilities/localstack-sdks/python-sdk /aws/configuration/localstack-sdks/python-sdk 301
/aws/capabilities/localstack-sdks/python-sdk/ /aws/configuration/localstack-sdks/python-sdk 301
-/aws/capabilities/localsurf /aws/configuration/localsurf 301
-/aws/capabilities/localsurf/ /aws/configuration/localsurf 301
+/aws/capabilities/localsurf /aws/customization/networking/ 301
+/aws/capabilities/localsurf/ /aws/customization/networking/ 301
/aws/capabilities/networking /aws/configuration/networking 301
/aws/capabilities/networking/ /aws/configuration/networking 301
/aws/capabilities/networking/accessing-endpoint-url /aws/configuration/networking/accessing-endpoint-url 301
diff --git a/scripts/redirects/_redirects b/scripts/redirects/_redirects
index da10a153..213b5ab6 100644
--- a/scripts/redirects/_redirects
+++ b/scripts/redirects/_redirects
@@ -121,7 +121,7 @@
/references/coverage/coverage_es/ /aws/services/es 301
/user-guide/integrations/sdks/javascript/ /aws/integrations/aws-sdks/net/javascript 301
/references/coverage/coverage_iam/ /aws/services/iam 301
-/user-guide/tools/localsurf/ /aws/tooling/localsurf 301
+/user-guide/tools/localsurf/ /aws/customization/networking/ 301
/user-guide/extensions/extensions-library/ /aws/tooling/extensions/extensions-library 301
/tutorials/ecs-ecr-container-app/ /aws/tutorials/ecs-ecr-container-app 301
/references/coverage/coverage_elastictranscoder/ /aws/services/elastictranscoder 301
diff --git a/scripts/redirects/redirects_config.json b/scripts/redirects/redirects_config.json
index 6f358056..50ccf690 100644
--- a/scripts/redirects/redirects_config.json
+++ b/scripts/redirects/redirects_config.json
@@ -612,7 +612,7 @@
},
{
"old_link": "/user-guide/tools/localsurf/",
- "new_link": "/aws/tooling/localsurf",
+ "new_link": "/aws/customization/networking",
"status_code": 301
},
{
diff --git a/src/content/docs/aws/configuration/config/configuration.md b/src/content/docs/aws/configuration/config/configuration.md
index 9ad76f33..f63006b1 100644
--- a/src/content/docs/aws/configuration/config/configuration.md
+++ b/src/content/docs/aws/configuration/config/configuration.md
@@ -197,14 +197,20 @@ This section covers configuration options that are specific to certain AWS servi
| Variable | Example Values | Description |
| - | - | - |
-| `EKS_K3S_FLAGS` | | Customize the `k3s` cluster created by LocalStack to emulate EKS clusters. |
+| `K3S_FLAGS` | | Customize the `k3s` cluster created by LocalStack to emulate EKS clusters. (formerly `EKS_K3S_FLAGS`, still accepted as a deprecated alias) |
| `EKS_LOADBALANCER_PORT` | `8081` (default) | Local port on which the Kubernetes load balancer is exposed on the host. |
-| `EKS_K3S_IMAGE_TAG` | `v1.31.5-k3s1` (default) | Custom tag of the `rancher/k3s` image used to spin up Kubernetes clusters locally. |
-| `EKS_K8S_PROVIDER` | `k3s` (default)\|`local` | The k8s provider which should be used to start the k8s cluster backing EKS. For more information on the providers, please see [Elastic Kubernetes Service (EKS)](/aws/services/eks) |
-| `EKS_K3S_IMAGE_REPOSITORY` | `rancher/k3s` (default) | Custom repository of the `rancher/k3s` image used to spin up Kubernetes clusters locally. |
-| `EKS_START_K3D_LB_INGRESS` | `0` (default) | Whether to start the k3d load balancer and Traefik ingress controller automatically when creating an EKS cluster. Set to `1` to enable. |
+| `K3S_IMAGE_TAG` | `v1.31.5-k3s1` (default) | Custom tag of the `rancher/k3s` image used to spin up Kubernetes clusters locally. (formerly `EKS_K3S_IMAGE_TAG`, still accepted as a deprecated alias) |
+| `MANAGED_K8S_PROVIDER` | `k3s` (default)\|`local` | The k8s provider which should be used to start the k8s cluster backing EKS. For more information on the providers, please see [Elastic Kubernetes Service (EKS)](/aws/services/eks) (formerly `EKS_K8S_PROVIDER`, still accepted as a deprecated alias) |
+| `K3S_IMAGE_REPOSITORY` | `rancher/k3s` (default) | Custom repository of the `rancher/k3s` image used to spin up Kubernetes clusters locally. (formerly `EKS_K3S_IMAGE_REPOSITORY`, still accepted as a deprecated alias) |
+| `K3D_START_LB_INGRESS` | `0` (default) | Whether to start the k3d load balancer and Traefik ingress controller automatically when creating an EKS cluster. Set to `1` to enable. (formerly `EKS_START_K3D_LB_INGRESS`, still accepted as a deprecated alias) |
| `EKS_PERSIST_CLUSTER_CONTENTS` | `0` (default) | When Persistence is enabled or when saving/loading Cloud Pods, this flag can be used to control whether the content deployed to EKS clusters will be persisted. Set to `1` to enable. |
-| `EKS_K3D_CLUSTER_TOKEN` | `localstack-k3d-cluster-token` (default) | Token used to authenticate agent nodes joining a k3d-backed EKS cluster. Setting an explicit token ensures consistent node authentication across k3d versions, which is required for dynamic agent assignment. Can be overridden via the `EKS_K3D_CLUSTER_TOKEN` environment variable. |
+| `K3D_CLUSTER_TOKEN` | `localstack-k3d-cluster-token` (default) | Token used to authenticate agent nodes joining a k3d-backed EKS cluster. Setting an explicit token ensures consistent node authentication across k3d versions, which is required for dynamic agent assignment. (formerly `EKS_K3D_CLUSTER_TOKEN`, still accepted as a deprecated alias) |
+
+:::note
+The EKS configuration variables were renamed to cloud-agnostic names since they're shared across cloud emulators (AWS EKS / Azure AKS). The previous `EKS_*`, `LOCALSTACK_K8S_*`, and `LAMBDA_K8S_*` names still work as deprecated aliases and will be removed in a future release.
+
+If you configure these options through the LocalStack CLI **v1**, keep the `LOCALSTACK_` prefix on the new names (e.g. `LOCALSTACK_MANAGED_K8S_PROVIDER`). The CLI v1 only forwards host environment variables prefixed with `LOCALSTACK_` into the container, where the prefix is stripped to yield the in-container variable (`MANAGED_K8S_PROVIDER`).
+:::
### ElastiCache
@@ -462,7 +468,6 @@ To learn more about these configuration options, see [DNS Server](/aws/configura
| Variable | Example Values | Description |
|----------------------|----------------|-------------|
-| `ACTIVATE_PRO` | `0` \| `1` (default) | Whether Pro should be activated or not. This is set to true by default if using the `localstack/localstack-pro` container image. If set to `1`, LocalStack will fail to start if the license key activation did not work. If set to `0`, an attempt is made to start LocalStack without Pro features. |
| `LOCALSTACK_AUTH_TOKEN` | | [Auth token](/aws/getting-started/auth-token) to activate LocalStack for AWS. |
| `LOCALSTACK_API_KEY` | | **Deprecated since 3.0.0** API key to activate LocalStack for AWS.
**Use the `LOCALSTACK_AUTH_TOKEN` instead (except for [CI environments](/aws/integrations/continuous-integration/)).** |
| `LOG_LICENSE_ISSUES` | `0` \| `1` (default) | Whether to log issues with the license activation to the console. |
diff --git a/src/content/docs/aws/configuration/index.mdx b/src/content/docs/aws/configuration/index.mdx
index ed3684e5..fb77458e 100644
--- a/src/content/docs/aws/configuration/index.mdx
+++ b/src/content/docs/aws/configuration/index.mdx
@@ -13,7 +13,7 @@ LocalStack provides a number of configuration options and supporting tools that
It brings together the configuration options that control LocalStack's behavior, networking setup for reaching your services, the LocalStack Web App, the LocalStack SDKs for programmatic access, and the extensions mechanism for adding new functionality.
-You will also find guides for several standalone tools: the [DNS Server](/aws/configuration/dns-server), [Testing Utils](/aws/configuration/testing-utils), the [LocalStack Docker Extension](/aws/configuration/localstack-docker-extension), and [LocalSurf](/aws/configuration/localsurf).
+You will also find guides for several standalone tools: the [DNS Server](/aws/configuration/dns-server), [Testing Utils](/aws/configuration/testing-utils), and the [LocalStack Docker Extension](/aws/configuration/localstack-docker-extension).
This plugin is experimental and still under active development.
-> Please report any issues or feature requests on our [GitHub repository](https://github.com/localstack/local-surf).
-
-## Installation
-
-This extension is not yet available in the Chrome Web Store, but can be installed directly from source.
-Clone the repository on your local machine to get started:
-
-```bash
-git clone git@github.com:localstack/local-surf.git
-```
-
-Head over to `chrome://extensions/` in Chrome, then select `"Load unpacked"` and point to the directory where the source code files are stored on the disk.
-
-Once installed, a new icon should appear in the Chrome extensions bar.
-When clicking on the icon, the plugin can be enabled/disabled by toggling the **Enable local mode** checkbox.
-
-
-
-
-## Usage
-
-To illustrate how the plugin works, we use the AWS [Serverlesspresso](https://github.com/aws-samples/serverless-coffee-workshop) sample application.
-This app consists of various backend components (e.g., DynamoDB tables, Lambda functions, Cognito user pools, etc), as well as a hosted Web app user interface (UI) that can be used to interact with the backend components.
-
-We can deploy the backend components to LocalStack directly, using the `samlocal` command line interface (CLI):
-
-```bash
-samlocal build
-samlocal deploy
-```
-
-Once deployed, the `samlocal` CLI will print out a display app URL which will look similar to the following:
-
-```bash
-Key DisplayAppURI
-Description The URL for the Display App
-Value https://workshop-display.serverlesscoffee.com/?region=us-east-1&userPoolId=us-east-1_43c9800e64c84467aa0abdb102e226ef&userPoolWebClientId=vr9aw2jr7iv36ezwaaqlzzkvbp&poolId=us-east-1:95dc88d0-1029-48fe-ba7b-1e6a9741bfc5&host=localhost.localstack.cloud&orderManagerEndpoint=https://fapencq0ue.execute-api.amazonaws.com:4566/Prod&APIGWEndpointValidatorService=https://psmdc7b1lv.execute-api.amazonaws.com:4566/Prod&APIGWEndpointConfigService=https://hw7yw61ba7.execute-api.amazonaws.com:4566/Prod
-```
-
-We can then open the URL in the browser and confirm the configurations in the form dialog:
-
-
-
-Once confirmed, we are being forwarded to a signin screen, which uses an AWS Cognito user pool to manage authentication:
-
-
-
-After clicking **Sign In** in this form, we can see that the browser makes a request to LocalStack (to `localhost.localstack.cloud`, which is a domain name that resolves to `127.0.0.1`).
-
-
-
-This sample demonstrates how we can take an existing Web application, without any modification, and make it talk to the LocalStack APIs directly from the browser via the LocalSurf plugin.
-
-## Note
-
-Use this extension at your own risk - it is provided on an as-is basis, **without** warranties or conditions of **any** kind.
-In particular, it is your obligation to ensure that the use of this extension is compliant with the user license agreement and the terms & conditions of Amazon Web Services (AWS) and their services.
\ No newline at end of file
diff --git a/src/content/docs/aws/developer-tools/app-inspector/index.md b/src/content/docs/aws/developer-tools/app-inspector/index.md
index e3f5cd5f..60138e60 100644
--- a/src/content/docs/aws/developer-tools/app-inspector/index.md
+++ b/src/content/docs/aws/developer-tools/app-inspector/index.md
@@ -64,6 +64,8 @@ The interface enables you to trace the flow of operations, identify relationship

+Use the search box above the operations list to filter by any text fragment, such as a resource name, an ARN segment, a status message, or a payload snippet. Search is case-insensitive, matches substrings anywhere in the field, and covers the full result set rather than just the operations currently loaded on screen.
+
### Graph View and Operation Details
Click **View Detail** to visualize the relationships between AWS services in your application as an interactive graph. Each node represents a service, and each edge represents a call between them, making it easy to follow the path of a request across your architecture.
diff --git a/src/content/docs/aws/developer-tools/running-localstack/lstk.mdx b/src/content/docs/aws/developer-tools/running-localstack/lstk.mdx
index 9e04183c..0c4829da 100644
--- a/src/content/docs/aws/developer-tools/running-localstack/lstk.mdx
+++ b/src/content/docs/aws/developer-tools/running-localstack/lstk.mdx
@@ -15,13 +15,10 @@ import { Tabs, TabItem } from '@astrojs/starlight/components';
It provides a built-in terminal UI (TUI) for interactive use and plain text output for CI/CD pipelines and scripting.
`lstk` handles the full emulator lifecycle: authentication, pulling the Docker image, starting, stopping, and restarting the container, streaming logs, and checking status.
-It can also save and load emulator state — as local snapshots or Cloud Pods — reset running state, run AWS CLI commands against the emulator, and manage the on-disk volume.
+It can also save and load emulator state — as local snapshots, Cloud Pods, or in your own S3 bucket — reset running state, and manage the on-disk volume.
+It proxies your existing tooling — the AWS and Azure CLIs, Terraform, AWS CDK, and the AWS SAM CLI — so they run directly against LocalStack.
Running `lstk` with no arguments takes you through the entire startup flow automatically.
-:::note
-`lstk` supports core lifecycle commands (`start`, `stop`, `restart`, `logs`, `status`) along with state management via snapshots (`snapshot save`/`snapshot load`, `reset`) and an AWS CLI proxy (`lstk aws`).
-:::
-
## Prerequisites
- [Docker](https://docs.docker.com/get-docker/) installed and running.
@@ -175,7 +172,10 @@ The default `config.toml` created on first run:
type = "aws" # Emulator type. Supported: "aws", "snowflake", "azure"
tag = "latest" # Docker image tag, e.g. "latest", "2026.4"
port = "4566" # Host port the emulator will be accessible on
+# image = "" # Override the default Docker image (internal registry mirror or offline image)
# volume = "" # Host directory for persistent state (default: OS cache dir)
+# volumes = [] # Docker-style "host:container[:ro]" bind mounts (see below)
+# snapshot = "" # Snapshot REF auto-loaded after the AWS emulator starts (e.g. "pod:my-baseline")
# env = [] # Named environment profiles to apply (see [env.*] sections below)
```
@@ -186,7 +186,10 @@ port = "4566" # Host port the emulator will be accessible on
| `type` | string | `"aws"` | Emulator type. One of `"aws"`, `"snowflake"`, `"azure"`. Run a single `[[containers]]` block at a time. See [Emulator types](#emulator-types). |
| `tag` | string | `"latest"` | Docker image tag (`"latest"`, `"2026.4"`, etc.). Useful for pinning a specific version. Zero-padded months (`"2026.04"`) are normalized to `"2026.4"`. |
| `port` | string | `"4566"` | Host port the emulator listens on (1–65535). The in-container port is always `4566`. |
-| `volume` | string | (OS cache) | Host directory for persistent emulator state. Defaults to `/lstk/volume/`. |
+| `image` | string | (unset) | Override the default Docker image, e.g. an internal-registry mirror or a locally loaded offline image. If it already carries a tag, `tag` is dropped; otherwise `tag` (or `latest`) is appended. When unset, the default `localstack/` image is used. |
+| `volume` | string | (OS cache) | Host directory for persistent emulator state (mounted at `/var/lib/localstack`). Defaults to `/lstk/volume/`. Used verbatim (no path resolution). |
+| `volumes`| string[] | `[]` | Docker-style `"host:container[:ro]"` bind mounts (e.g. Snowflake init hooks). May also carry the persistence mount (the entry targeting `/var/lib/localstack`); relative host sources are resolved against the config file's directory and a leading `~/` is expanded. See [Volume mounts](#volume-mounts). |
+| `snapshot` | string | (unset) | Snapshot `REF` (a `pod:`, local path, or `s3://` location) auto-loaded after the AWS emulator starts. See [Auto-loading a snapshot on start](#auto-loading-a-snapshot-on-start). |
| `env` | string[] | `[]` | List of named environment profiles to inject into the container (see below). |
:::note
@@ -211,7 +214,9 @@ On the first interactive run, `lstk` prompts you to pick an emulator (`a` for AW
In non-interactive mode the default `aws` emulator is used.
Lifecycle commands operate on the emulators defined in your `config.toml`.
-Run a single `[[containers]]` block at a time; the AWS-specific commands (`status` resources, `aws`, `snapshot`, `reset`, `setup aws`) require an `aws` emulator to be configured.
+Run a single `[[containers]]` block at a time.
+The AWS-specific commands — the resources listing in `status`, `reset`, the `aws` CLI proxy, the IaC proxies (`terraform`, `cdk`, `sam`), and `setup aws` — require an `aws` emulator; `az` and `setup azure` require an `azure` emulator.
+`snapshot` (`save`/`load`/`list`/`remove`/`show`) works with any emulator, though some snapshot features are fully supported only on the AWS emulator for now — see the snapshot docs for your emulator for details.
:::note
The AWS emulator's license is validated by `lstk` before the container starts.
@@ -250,6 +255,32 @@ If you reference an `env` profile name that doesn't exist in your config, `lstk`
In addition to your custom profiles, `lstk` always injects several variables into the container.
See [Container-injected variables](#container-injected-variables) for the full list.
+### Volume mounts
+
+Each `[[containers]]` block accepts a `volumes` list of Docker-style `"host:container[:ro]"` bind specs, in addition to the singular `volume` field.
+This is useful for mounting initialization hooks (scripts run at the container lifecycle stages under `/etc/localstack/init/{boot,start,ready,shutdown}.d`) or any other host path into the emulator.
+
+The example below uses `volumes` to map multiple mounts — a `ready`-stage init-hook script and the persistence directory:
+
+```toml
+[[containers]]
+type = "aws"
+port = "4566"
+volumes = [
+ "./init/ready.d/init.sh:/etc/localstack/init/ready.d/init.sh", # ready init hook
+ "./data:/var/lib/localstack", # persistence mount
+]
+```
+
+Init-hook script naming and behavior differ per emulator; see the Initialization Hooks guide for your emulator ([AWS](/aws/configuration/config/initialization-hooks/), [Snowflake](/snowflake/capabilities/init-hooks/)) for the full details.
+
+Resolution rules:
+
+- Relative host sources (`./data`) are resolved against the **directory of the config file** that declared them, and a leading `~/` is expanded. This is required because the Docker SDK treats a non-absolute source as a *named volume* rather than a bind mount.
+- The entry whose container target is `/var/lib/localstack` defines the persistence directory — the same one `lstk volume path` and `lstk volume clear` act on. Precedence: a `volumes` entry targeting `/var/lib/localstack` → the legacy `volume` field → the default OS cache directory.
+- `volume` and `volumes` overlap only for the persistence mount. The legacy `volume` value is used **verbatim** (no path resolution); `volumes` sources are resolved. Setting the persistence directory via both `volume` and a `volumes` entry with different sources is a validation error.
+- The persistence directory is created automatically if missing; other `volumes` sources are expected to already exist (they are typically files, such as init hooks) and `lstk` errors if one is missing.
+
### Using a project-local config
Place a `.lstk/config.toml` in your project directory.
@@ -300,7 +331,11 @@ lstk start --non-interactive
| Option | Description |
|:--------------------|:-----------------------------------------------------------------------------|
| `--persist` | Persist emulator state across restarts (sets `LOCALSTACK_PERSISTENCE=1` in the container) |
-| `--non-interactive` | Disable the interactive TUI and use plain output |
+| `--snapshot [` | Snapshot `REF` to auto-load once the emulator starts, overriding the `snapshot` config field for this run |
+| `--no-snapshot` | Skip auto-loading the configured snapshot for this run |
+| `--non-interactive` | Disable the interactive TUI and use plain output (a global flag) |
+
+Host environment variables prefixed with `LOCALSTACK_` are forwarded to the emulator on start (the host `LOCALSTACK_AUTH_TOKEN` is dropped so it cannot override the token `lstk` resolves). See [Container-injected variables](#container-injected-variables).
By default the emulator starts with a fresh state on every run.
Pass `--persist` to keep data across restarts: `lstk` injects `LOCALSTACK_PERSISTENCE=1` into the container so state is written to the mounted [`volume`](#config-field-reference) and reloaded on the next start.
@@ -316,6 +351,23 @@ lstk start --persist
For finer-grained control, you can also set `PERSISTENCE = "1"` in an environment profile (see [Passing environment variables to the container](#passing-environment-variables-to-the-container)).
:::
+#### Auto-loading a snapshot on start
+
+The AWS emulator can auto-load a snapshot as it comes up.
+Set the `snapshot` field on the AWS `[[containers]]` block to any load `REF` — a `pod:` Cloud Pod, a local snapshot path, or an `s3://` location:
+
+```toml
+[[containers]]
+type = "aws"
+port = "4566"
+snapshot = "pod:my-baseline"
+```
+
+The snapshot is loaded only when the emulator is **freshly started** this run (it is skipped when the emulator is already running).
+Override the configured `REF` for a single run with `lstk start --snapshot ][`, or skip auto-loading entirely with `lstk start --no-snapshot`.
+An invalid `REF` fails before the emulator starts.
+`snapshot save` never writes this field back — it is set manually.
+
### `stop`
Stop the running LocalStack emulator.
@@ -470,15 +522,135 @@ By default, `lstk` probes whether `localhost.localstack.cloud` resolves to `127.
Set [`LOCALSTACK_HOST`](#environment-variables) to override the host:port used to reach LocalStack and skip the DNS probe.
The port comes from the AWS container's `port` in `config.toml` (default `4566`).
+### `az`
+
+Run Azure CLI commands against the running LocalStack Azure emulator.
+`lstk az ` runs `az ` with an isolated `AZURE_CONFIG_DIR` in which a custom Azure cloud (`LocalStack`) is registered against LocalStack's endpoints, so your global `~/.azure` configuration is left untouched and plain `az` keeps talking to real Azure.
+
+```bash
+lstk az group list
+lstk az storage account list
+```
+
+Run [`lstk setup azure`](#setup-azure) once before using this mode.
+`lstk az` requires the `az` CLI on your `PATH`, a healthy Docker runtime, a running Azure emulator, and that `*.localhost.localstack.cloud` resolves to `127.0.0.1` (the Azure emulator serves its endpoints under that host). Set [`LOCALSTACK_HOST`](#environment-variables) to override the host.
+
+Everything after `lstk az` is forwarded verbatim to the host `az` binary; its exit code and `stdout`/`stderr` pass through unchanged.
+
+#### `az start-interception` / `az stop-interception`
+
+An opt-in interception mode mutates your **global** `~/.azure` configuration so plain `az` (in any terminal or script) targets LocalStack, then switches it back.
+
+```bash
+lstk az start-interception # global 'az' now targets LocalStack
+lstk az stop-interception # switch back to real Azure
+```
+
+- `start-interception` registers and activates the `LocalStack` cloud in `~/.azure` and disables instance discovery, so existing `az` scripts run unmodified against LocalStack. It is independent of `lstk setup azure`.
+- `stop-interception` switches the active cloud back to `AzureCloud` (override with `--cloud `) and re-enables instance discovery — but only if `LocalStack` is still the active cloud, to avoid clobbering an unrelated selection.
+
+### `terraform`
+
+Proxy Terraform commands to LocalStack, injecting LocalStack endpoints as AWS provider overrides so your existing configuration runs unchanged. Aliased as `lstk tf`.
+
+```bash
+lstk terraform init
+lstk terraform --region us-west-2 plan
+lstk tf apply
+```
+
+`lstk`-specific flags must appear **before** the Terraform subcommand:
+
+| Flag | Description |
+|:--------------------|:------------------------------------------------------------------|
+| `--region ` | Deployment region (default `us-east-1`; falls back to `AWS_REGION`). |
+| `--account ` | Target AWS account id, 12 digits (default `test`; falls back to `AWS_ACCESS_KEY_ID`). |
+
+Commands that don't contact AWS (`fmt`, `validate`, `version`, and `init` when no S3 backend is declared) run without a running emulator; everything else requires the AWS emulator to be running and Docker healthy.
+
+Supported environment variables:
+
+| Variable | Description |
+|:-----------------------------|:---------------------------------------------------------------------|
+| `AWS_ENDPOINT_URL` | Override the auto-resolved LocalStack endpoint. |
+| `LSTK_TF_CMD` | Terraform binary to invoke (e.g. `tofu`; default `terraform`). |
+| `LSTK_TF_OVERRIDE_FILE_NAME` | Provider-override file name (default `localstack_providers_override.tf`). |
+| `LSTK_TF_DRY_RUN` | Generate the override file but do not run Terraform. |
+| `AWS_REGION` | Fallback for `--region`. |
+| `AWS_ACCESS_KEY_ID` | Fallback for `--account`. |
+
+### `cdk`
+
+Proxy AWS CDK commands to the running LocalStack emulator.
+Requires the AWS CDK CLI version `2.177.0` or newer on your `PATH`.
+
+```bash
+lstk cdk bootstrap
+lstk cdk --region us-west-2 deploy
+lstk cdk synth
+```
+
+CDK always targets the default LocalStack account `000000000000`; there is **no** `--account` flag (passing one is an error). The `--region ` flag (default `us-east-1`, falling back to `AWS_REGION`) must appear before the CDK action.
+
+Offline subcommands (e.g. `synth`) run without a running emulator; deploy-time commands require the AWS emulator to be running.
+
+Supported environment variables:
+
+| Variable | Description |
+|:----------------------|:-----------------------------------------------------|
+| `AWS_ENDPOINT_URL` | Override the auto-resolved LocalStack endpoint. |
+| `AWS_ENDPOINT_URL_S3` | Override the auto-derived S3 endpoint. |
+| `LSTK_CDK_CMD` | CDK binary to invoke (default `cdk`). |
+| `AWS_REGION` | Fallback for `--region`. |
+
+:::note
+If `localhost.localstack.cloud` does not resolve and `lstk` falls back to `127.0.0.1`, CDK's S3 asset operations (bootstrap, asset deploys) may fail virtual-host addressing. Ensure the host resolves, or set `AWS_ENDPOINT_URL`/`AWS_ENDPOINT_URL_S3` to a virtual-host-capable endpoint.
+:::
+
+### `sam`
+
+Proxy AWS SAM CLI commands to the running LocalStack emulator.
+Requires the AWS SAM CLI version `1.95.0` or newer on your `PATH` (older versions ignore `AWS_ENDPOINT_URL` and would target real AWS).
+
+```bash
+lstk sam build
+lstk sam --region us-west-2 deploy
+lstk sam validate
+```
+
+`lstk`-specific flags must appear **before** the SAM action:
+
+| Flag | Description |
+|:--------------------|:------------------------------------------------------------------|
+| `--region ` | Deployment region (default `us-east-1`; falls back to `AWS_REGION`). |
+| `--account ` | Target AWS account id, 12 digits (default `000000000000`; falls back to `AWS_ACCESS_KEY_ID`). |
+
+Offline subcommands (e.g. `build`, `validate`) run without a running emulator; deploy-time commands require the AWS emulator to be running.
+
+Supported environment variables:
+
+| Variable | Description |
+|:----------------------|:------------------------------------------------|
+| `AWS_ENDPOINT_URL` | Override the auto-resolved LocalStack endpoint. |
+| `AWS_ENDPOINT_URL_S3` | Override the S3 endpoint. |
+| `LSTK_SAM_CMD` | SAM binary to invoke (default `sam`). |
+| `AWS_REGION` | Fallback for `--region`. |
+| `AWS_ACCESS_KEY_ID` | Fallback for `--account`. |
+
+:::note
+Compared to `samlocal`, image/container-based Lambda (ECR) deploys and nested CloudFormation stacks are not supported; use `samlocal` for those workflows.
+:::
+
### `snapshot`
Manage emulator snapshots.
-A snapshot captures the running AWS emulator's state, either as a local file on disk or as a Cloud Pod on the LocalStack platform.
-The `snapshot` command groups four subcommands — `save`, `load`, `list`, and `remove`. The first two are also exposed as the top-level aliases `lstk save` and `lstk load`.
+A snapshot captures the running emulator's state — as a local file on disk, a Cloud Pod on the LocalStack platform, or an object in your own S3 bucket.
+The `snapshot` command groups five subcommands — `save`, `load`, `list`, `remove`, and `show`. The first two are also exposed as the top-level aliases `lstk save` and `lstk load`.
:::note
-`snapshot`, `save`, `load`, and [`reset`](#reset) operate on the **AWS emulator** only.
-If no AWS emulator is configured or running, they error out (`snapshot is only supported for the AWS emulator`).
+`snapshot` works with any emulator, but the AWS emulator is the well-tested path.
+For the Snowflake and Azure emulators, snapshot support is still maturing, so `snapshot` commands print a warning (`Snapshot support for the emulator is experimental and not fully tested.`) — results may be incomplete.
+[`reset`](#reset), by contrast, operates on the **AWS emulator only** and errors out otherwise (`reset is only supported for the AWS emulator`).
:::
#### `snapshot save`
@@ -495,18 +667,29 @@ lstk snapshot save ./my-snapshot
# Save to a Cloud Pod on the LocalStack platform (requires auth)
lstk snapshot save pod:my-baseline
+
+# Save to your own S3 bucket (pod name optional, auto-generated when omitted)
+lstk snapshot save my-pod s3://my-bucket/prefix
+lstk snapshot save my-pod s3://my-bucket/prefix --profile my-aws-profile
```
-The optional `[destination]` argument takes one of three forms:
+The optional `[destination]` argument takes one of these forms:
| Destination | Description |
|:-------------------|:------------------------------------------------------------------------------------------------|
-| (omitted) | Auto-generates a timestamped snapshot file in the current directory. |
-| local path | Writes a snapshot archive to that path. |
+| (omitted) | Auto-generates a timestamped `.snapshot` file in the current directory. |
+| local path | Writes a snapshot file to that path (the `.snapshot` extension is forced). |
| `pod:` | Saves a Cloud Pod to the LocalStack platform. Requires authentication. |
+| `s3://bucket/prefix` | Saves into your own S3 bucket. The pod name is a separate positional (auto-generated when omitted). |
Pod operations require an auth token (`LOCALSTACK_AUTH_TOKEN` or a prior `lstk login`); local-file snapshots do not.
+| Option | Description |
+|:--------------------|:---------------------------------------------------------------------------------------------|
+| `--profile ` | AWS profile to read S3 credentials from (S3 destinations only). |
+
+For S3 destinations, credentials follow AWS CLI precedence: `--profile ` wins, otherwise the static `AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY` (with optional `AWS_SESSION_TOKEN`), otherwise the profile named by `AWS_PROFILE`. Only static credentials are supported (no SSO, assume-role, or credential-process). Never put credentials in the URL. See [S3 remotes](#s3-remotes).
+
#### `snapshot load`
Load a snapshot into the emulator, **auto-starting it first** if it is not already running.
@@ -519,15 +702,19 @@ lstk snapshot load ./checkpoint
# Load from a Cloud Pod (requires auth)
lstk snapshot load pod:my-baseline
+# Load from your own S3 bucket (pod name required)
+lstk snapshot load my-pod s3://my-bucket/prefix --profile my-aws-profile
+
# Control how the snapshot merges with running state
lstk snapshot load pod:my-baseline --merge=overwrite
```
-The `REF` argument is required and identifies a local path/name or a `pod:` Cloud Pod.
+The `REF` argument is required and identifies a local path/name, a `pod:` Cloud Pod, or (with a separate pod-name positional) an `s3://` location. Older `.zip` snapshots saved by earlier `lstk` versions are still accepted.
| Option | Description |
|:---------------------|:--------------------------------------------------------------------------------------------------------|
| `--merge ` | How the loaded state combines with running state. One of `account-region-merge` (default), `overwrite`, `service-merge`. |
+| `--profile ` | AWS profile to read S3 credentials from (S3 sources only; see [S3 remotes](#s3-remotes)). |
- `account-region-merge` (default): the snapshot wins on any `(service, account, region)` overlap.
- `overwrite`: running state is reset first, then the snapshot is imported onto a clean state.
@@ -552,11 +739,20 @@ lstk snapshot list
# Every snapshot in your organization
lstk snapshot list --all
+
+# List snapshots in your own S3 bucket (requires a running emulator)
+lstk snapshot list s3://my-bucket/prefix --profile my-aws-profile
```
-| Option | Description |
-|:--------|:------------------------------------------------------------|
-| `--all` | List all snapshots in your organization, not just your own. |
+| Option | Description |
+|:-------------------|:------------------------------------------------------------|
+| `--all` | List all snapshots in your organization, not just your own. |
+| `--profile ` | AWS profile to read S3 credentials from (S3 listings only). |
+
+:::note
+`lstk snapshot list` (Cloud Pods) queries the LocalStack platform and does not need a running emulator.
+`lstk snapshot list s3://…`, by contrast, queries a **running emulator** (which performs the S3 transfer), so an emulator must be up. See [S3 remotes](#s3-remotes).
+:::
#### `snapshot remove`
@@ -577,6 +773,27 @@ The required `REF` argument must be a `pod:` Cloud Pod reference.
|:----------|:----------------------------------------------------------------------|
| `--force` | Skip the confirmation prompt. Required when running non-interactively. |
+#### `snapshot show`
+
+Show metadata for a single Cloud Pod snapshot on the LocalStack platform: name, created date, size, LocalStack version, message, services, and per-service resource counts (resource counts render only when the platform has them for that snapshot).
+Cloud-only and requires authentication.
+
+```bash
+lstk snapshot show pod:my-baseline
+```
+
+The required `REF` argument must be a `pod:` Cloud Pod reference.
+
+#### S3 remotes
+
+`snapshot save`, `load`, and `list` can target your own S3 bucket with an `s3://bucket/prefix` location.
+The pod name (the snapshot's identity within the bucket) is a positional argument separate from the `s3://` location — required for `load`, auto-generated for `save` when omitted, and unused for `list`.
+
+- **Credentials** follow AWS CLI precedence: `--profile ` wins, otherwise static `AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY` (with optional `AWS_SESSION_TOKEN`), otherwise the profile named by `AWS_PROFILE`. Only static credentials are supported (no SSO, assume-role, or credential-process). Credentials are never accepted in the URL.
+- **The emulator performs the transfer**, not the CLI; credentials are passed as ephemeral parameters on each operation and are never persisted.
+- Before each operation, `lstk` runs a bucket-existence pre-flight check and errors out rather than letting the emulator auto-create a bucket on a typo.
+- `remove`, `show`, and snapshot versions are **not** yet supported on S3 remotes; ORAS and other remote types are not supported.
+
### `reset`
Discard the running AWS emulator's in-memory state (all created resources such as S3 buckets and Lambda functions are dropped).
@@ -731,22 +948,26 @@ If a LocalStack emulator is still running after logout, `lstk` prints a note rem
### `setup`
Set up CLI integration for an emulator type.
-`lstk setup` is a grouping command with no action of its own; the work is done by its subcommand.
-Currently only AWS is supported.
+`lstk setup` is a grouping command with no action of its own; the work is done by its subcommands (`aws` and `azure`).
```bash
lstk setup aws
+lstk setup azure
```
#### `setup aws`
Create or update a `localstack` profile in `~/.aws/config` and `~/.aws/credentials` so the AWS CLI and SDKs can target LocalStack.
-This command requires an interactive terminal and prompts before making any changes.
```bash
lstk setup aws
+lstk setup aws --force
```
+| Option | Description |
+|:----------|:--------------------------------------------------------------------------------------------------|
+| `--force` | Skip the confirmation prompt and overwrite an existing `localstack` profile whose values differ. |
+
It writes the following profile (existing unrelated profiles are preserved):
```ini
@@ -775,10 +996,10 @@ The port comes from your AWS emulator's configured `port` (default `4566`); if n
If the `localstack` profile is already configured correctly, `lstk` reports `LocalStack AWS profile is already configured.` and makes no changes.
+On an interactive terminal, `setup aws` prompts (Y/n) before writing. Overwriting an existing `localstack` profile whose values differ requires `--force` (which also skips the prompt); writing a fresh profile, completing a partial one, or leaving an already-correct profile unchanged never needs it.
+
:::note
-`setup aws` requires an interactive terminal.
-In non-interactive mode (piped output, CI, or `--non-interactive`) it fails with `setup aws requires an interactive terminal`.
-There is no flag to auto-confirm.
+In non-interactive mode (piped output, CI, or `--non-interactive`), `setup aws` does not prompt — it writes the profile with defaults and exits `0`. A write or check failure returns a non-zero exit code so automation fails loudly.
:::
:::caution
@@ -786,6 +1007,19 @@ There is no flag to auto-confirm.
It still works and behaves identically, but you should use `lstk setup aws` instead.
:::
+#### `setup azure`
+
+Prepare an isolated Azure CLI config directory (under the `lstk` config dir, via `AZURE_CONFIG_DIR`) that routes [`lstk az`](#az) commands to the LocalStack Azure emulator.
+It registers a custom Azure cloud (`LocalStack`) pointing at LocalStack's Azure endpoints, activates it, disables Azure CLI instance discovery and telemetry, and performs a one-time dummy service-principal login.
+Your global `~/.azure` configuration is left untouched.
+
+```bash
+lstk setup azure
+```
+
+Requires the `az` CLI on your `PATH` and a running LocalStack Azure emulator.
+Run this once; afterwards use `lstk az ` to run Azure CLI commands against LocalStack.
+
### `config`
Manage CLI configuration.
@@ -912,6 +1146,8 @@ These options are available for all commands:
| `--config ` | Path to a specific TOML config file |
| `--non-interactive` | Disable the interactive TUI, use plain output |
| `--persist` | Persist emulator state across restarts (on `start`/bare `lstk` and `restart`) |
+| `--snapshot ][` | Snapshot `REF` to auto-load after start (on `start`/bare `lstk`; overrides config for the run) |
+| `--no-snapshot` | Skip auto-loading the configured snapshot for the run (on `start`/bare `lstk`) |
| `-v`, `--version` | Print the version and exit |
| `-h`, `--help` | Print help and exit |
@@ -930,9 +1166,9 @@ lstk --non-interactive start
```
:::note
-`lstk login` and `lstk setup aws` require an interactive terminal.
+`lstk login` requires an interactive terminal.
If you need to authenticate in CI, set `LOCALSTACK_AUTH_TOKEN` instead.
-Commands that mutate state without prompting in CI (`reset`, `volume clear`) require `--force`.
+`setup aws` runs non-interactively (it writes the profile with defaults), but commands that mutate state without prompting in CI (`reset`, `volume clear`, `snapshot remove`) require `--force`.
:::
## Environment variables
diff --git a/src/content/docs/aws/developer-tools/security-testing/explainable-iam.md b/src/content/docs/aws/developer-tools/security-testing/explainable-iam.md
index cc2509a7..d3502795 100644
--- a/src/content/docs/aws/developer-tools/security-testing/explainable-iam.md
+++ b/src/content/docs/aws/developer-tools/security-testing/explainable-iam.md
@@ -9,8 +9,10 @@ sidebar:
## Introduction
-The IAM Policy Engine logs output related to failed policy evaluation directly to the LocalStack logs.
-You can enable `DEBUG=1` to gain visibility into these log messages, allowing you to identify the additional policies required for your request to succeed.
+When IAM enforcement denies a request, the IAM Policy Engine returns a descriptive denial message in the API response and records the same information in the LocalStack logs.
+These messages identify the action that was denied, the policy type responsible (identity-based policy, resource-based policy, permissions boundary, or service control policy), the specific policy document involved, and whether the denial was explicit or implicit.
+This helps you pinpoint the additional policies required for your request to succeed.
+Enable `DEBUG=1` to surface the full log output.
## Getting started
@@ -91,22 +93,21 @@ awslocal lambda create-function \
--zip-file fileb://function.zip
```
+The request is denied, and the error response names the exact action that was missing:
+
```bash
-An error occurred (AccessDeniedException) when calling the CreateFunction operation: Access to the specified resource is denied
+An error occurred (AccessDeniedException) when calling the CreateFunction operation: User: arn:aws:iam::000000000000:user/test-user is not authorized to perform: iam:PassRole on resource: arn:aws:iam::000000000000:role/lambda-role because no identity-based policy allows the iam:PassRole action
```
-You can inspect the LocalStack logs, to observe the presence of five log entries directly related to the denied request:
+The same information is written to the LocalStack logs.
+Inspect the logs to see the corresponding Policy Engine entry:
```bash
-INFO:localstack_ext.services.iam.policy_engine.handler: Request for service lambda for operation CreateFunction denied.
-DEBUG:localstack_ext.services.iam.policy_engine.handler: Necessary permissions for this action: ["Action 'lambda:CreateFunction' for 'arn:aws:lambda:us-east-1:000000000000:function:test-function'", "Action 'iam:PassRole' for 'arn:aws:iam::000000000000:role/lambda-role'"]
-DEBUG:localstack_ext.services.iam.policy_engine.handler: 0 permissions have been explicitly denied: []
-DEBUG:localstack_ext.services.iam.policy_engine.handler: 1 permissions have been explicitly allowed: ["Action 'lambda:CreateFunction' for 'arn:aws:lambda:us-east-1:000000000000:function:test-function'"]
-DEBUG:localstack_ext.services.iam.policy_engine.handler: 1 permissions have been implicitly denied: ["Action 'iam:PassRole' for 'arn:aws:iam::000000000000:role/lambda-role'"]
+2026-06-23T15:48:09.650 INFO --- [PoolThread-twisted.internet.reactor-2] localstack.pro.core.services.iam.policy_engine.handler : User: arn:aws:iam::000000000000:user/test-user is not authorized to perform: iam:PassRole on resource: arn:aws:iam::000000000000:role/lambda-role because no identity-based policy allows the iam:PassRole action
```
-Upon examination, it becomes apparent that the action `iam:PassRole` is not allowed; rather, it is implicitly denied for your user concerning the resource `arn:aws:iam::000000000000:role/lambda-role`.
-This implies that there is no explicit deny statement in the relevant policies, but there is also no allow statement, resulting in the implicit denial of the action.
+The message tells you that `iam:PassRole` is *implicitly* denied for your user on the resource `arn:aws:iam::000000000000:role/lambda-role`.
+This means there is no explicit deny statement in the relevant policies, but there is also no allow statement, so the action is denied by default.
You can incorporate this action into the policy.
### Incorporate the action into the policy
@@ -131,6 +132,28 @@ Edit the `policy_1.json` file to include the `iam:PassRole` action:
Re-run the Lambda [`CreateFunction`](https://docs.aws.amazon.com/lambda/latest/dg/API_CreateFunction.html) API.
You will notice that the request is now successful, and the function is created.
+## Reading denial messages
+
+Every denial message follows the same structure: the **principal** that made the request, the **action** it attempted, the **resource** it targeted, and the **cause** of the denial.
+The cause tells you both the policy type and whether the denial was explicit or implicit:
+
+- An **explicit deny** names the policy type and the specific policy document, for example `with an explicit deny in a service control policy: arn:aws:organizations::000000000000:policy/p-abc123`.
+- An **implicit deny** indicates that no policy allowed the action, for example `because no identity-based policy allows the s3:ListBucket action`.
+
+The Policy Engine evaluates identity-based policies, resource-based policies, permissions boundaries, and service control policies, and the denial message identifies whichever one is responsible.
+
+### Inline policies
+
+The logs go one step further for inline policies.
+On AWS, inline policies are reported anonymously, which makes denials caused by them difficult to trace.
+LocalStack instead names the specific inline policy responsible for the denial:
+
+```bash
+2026-06-23T15:57:15.197 INFO --- [PoolThread-twisted.internet.reactor-2] localstack.pro.core.services.iam.policy_engine.handler : User: arn:aws:sts::000000000000:assumed-role/role-881f9fff/TestSession is not authorized to perform: kms:DescribeKey on resource: arn:aws:kms:us-east-1:000000000000:key/ad764663-5e3d-4325-81de-c7e0964f7b7f with an explicit deny in a role inline policy: policy-fc33c780
+```
+
+Here, the request was blocked by an explicit deny in the inline policy `policy-fc33c780` attached to the assumed role, letting you go straight to the policy that needs changing.
+
## Soft Mode
Enabling `IAM_SOFT_MODE=1` allows you to review the logs and assess whether your requests would have been denied or granted while executing your entire stack without disruptions.
diff --git a/src/content/docs/aws/developer-tools/security-testing/iam-policy-enforcement.md b/src/content/docs/aws/developer-tools/security-testing/iam-policy-enforcement.md
index 17e21d24..e1369079 100644
--- a/src/content/docs/aws/developer-tools/security-testing/iam-policy-enforcement.md
+++ b/src/content/docs/aws/developer-tools/security-testing/iam-policy-enforcement.md
@@ -13,6 +13,10 @@ IAM Policy Enforcement feature can be used to test your security policies and cr
The environment configuration `ENFORCE_IAM=1` is required while starting LocalStack to enable this feature.
Per default, IAM enforcement is disabled, and all APIs can be accessed without authentication.
+When enabled, LocalStack evaluates identity-based policies, resource-based policies, permissions boundaries, and [service control policies](#service-control-policies) together to decide whether a request is allowed.
+When a request is denied, LocalStack returns a descriptive error that identifies the denied action and the policy responsible for the denial.
+See [Explainable IAM](/aws/developer-tools/security-testing/explainable-iam/) for a detailed look at these messages.
+
## Getting started
This guide is designed for users new to IAM Policy Enforcement and assumes basic knowledge of the AWS CLI and our [`awslocal`](https://github.com/localstack/awscli-local) wrapper script.
@@ -78,14 +82,15 @@ awslocal s3 mb s3://mybucket
```
```bash
-make_bucket failed: s3://mybucket An error occurred (AccessDeniedException) when calling the CreateBucket operation: Access to the specified resource is denied
+make_bucket failed: s3://mybucket An error occurred (AccessDeniedException) when calling the CreateBucket operation: User: arn:aws:iam::000000000000:user/test is not authorized to perform: s3:CreateBucket on resource: arn:aws:s3:::mybucket because no identity-based policy allows the s3:CreateBucket action
```
As anticipated, the attempt to create the bucket fails with an `AccessDeniedException` error, confirming that user `test` lacks the necessary permissions for this action.
+The error message names the denied action (`s3:CreateBucket`) and explains that no identity-based policy allows it.
You can view the LocalStack logs to validate the policy enforcement:
```bash
-2023-11-03T12:21:10.971 INFO --- [ asgi_gw_1] l.s.i.p.handler : Request for service 's3' by principal 'arn:aws:iam::000000000000:user/test' for operation 'CreateBucket' denied.
+2023-11-03T12:21:10.971 INFO --- [ asgi_gw_1] localstack.pro.core.services.iam.policy_engine.handler : User: arn:aws:iam::000000000000:user/test is not authorized to perform: s3:CreateBucket on resource: arn:aws:s3:::mybucket because no identity-based policy allows the s3:CreateBucket action
2023-11-03T12:21:10.972 INFO --- [ asgi_gw_1] localstack.request.aws : AWS s3.CreateBucket => 403 (AccessDenied)
```
@@ -122,6 +127,62 @@ You can view the LocalStack logs to validate the policy enforcement:
You can further use the IAM Policy Enforcement feature to test your Infrastructure as Code (IaC) deployments and ensure that your policies are correctly enforced.
If the IAM policies are not correctly enforced, you will get an unsuccessful response from the API call, and the LocalStack logs will provide you with the necessary information to debug the issue.
+## Service Control Policies
+
+Service Control Policies (SCPs) are a policy type managed by [AWS Organizations](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_scps.html).
+Unlike identity-based and resource-based policies, SCPs do not grant permissions on their own — they act as guardrails that define the maximum permissions available to the accounts they apply to.
+If an SCP does not allow an action (or explicitly denies it), no `Allow` in an identity-based policy can override that result.
+
+With `ENFORCE_IAM=1`, LocalStack evaluates SCPs alongside identity-based policies, resource-based policies, and permissions boundaries.
+This applies both to single-account access and to cross-account access, where a principal in one account acts on a resource owned by another.
+
+The steps below extend the walkthrough above: user `test` already has an identity-based policy that allows `s3:CreateBucket`.
+We will add an SCP that denies the action and confirm that the request is blocked despite the identity-based `Allow`.
+
+In **Terminal 1**, create an organization and a service control policy that denies `s3:CreateBucket`:
+
+```bash
+awslocal organizations create-organization --feature-set ALL
+```
+
+
+```bash
+awslocal organizations create-policy \
+ --name deny-create-bucket \
+ --type SERVICE_CONTROL_POLICY \
+ --description "Deny S3 bucket creation" \
+ --content '{"Version":"2012-10-17","Statement":[{"Effect":"Deny","Action":"s3:CreateBucket","Resource":"*"}]}'
+```
+
+Attach the SCP to the target account:
+
+```bash
+awslocal organizations attach-policy \
+ --policy-id \
+ --target-id
+```
+
+Back in **Terminal 2**, attempt to create the bucket again as user `test`:
+
+```bash
+awslocal s3 mb s3://mybucket
+```
+
+Even though the user's identity-based policy allows `s3:CreateBucket`, the SCP guardrail blocks the request, and the denial message names the responsible SCP:
+
+```bash
+make_bucket failed: s3://mybucket An error occurred (AccessDeniedException) when calling the CreateBucket operation: User: arn:aws:iam::000000000000:user/test is not authorized to perform: s3:CreateBucket on resource: arn:aws:s3:::mybucket with an explicit deny in a service control policy: arn:aws:organizations::000000000000:policy/
+```
+
+The LocalStack logs record the same information:
+
+```bash
+2023-11-03T12:30:44.512 INFO --- [ asgi_gw_1] localstack.pro.core.services.iam.policy_engine.handler : User: arn:aws:iam::000000000000:user/test is not authorized to perform: s3:CreateBucket on resource: arn:aws:s3:::mybucket with an explicit deny in a service control policy: arn:aws:organizations::000000000000:policy/
+2023-11-03T12:30:44.513 INFO --- [ asgi_gw_1] localstack.request.aws : AWS s3.CreateBucket => 403 (AccessDenied)
+```
+
+This confirms that the SCP overrides the identity-based `Allow`, matching the AWS evaluation order in which an SCP guardrail takes precedence.
+
## Feature coverage
The feature coverage is documented in the [IAM coverage documentation](/aws/developer-tools/security-testing/iam-coverage/).
diff --git a/src/content/docs/aws/enterprise/kubernetes/configuration.md b/src/content/docs/aws/enterprise/kubernetes/configuration.md
index df509e80..962783e6 100644
--- a/src/content/docs/aws/enterprise/kubernetes/configuration.md
+++ b/src/content/docs/aws/enterprise/kubernetes/configuration.md
@@ -11,9 +11,9 @@ When LocalStack runs on Kubernetes with the Kubernetes executor enabled, a set o
### Namespace
-By default, LocalStack creates child pods in the `default` namespace. Use `LOCALSTACK_K8S_NAMESPACE` to deploy them into a different namespace.
+By default, LocalStack creates child pods in the `default` namespace. Use `K8S_NAMESPACE` to deploy them into a different namespace.
```bash
-LOCALSTACK_K8S_NAMESPACE=localstack-workloads
+K8S_NAMESPACE=localstack-workloads
```
The namespace must already exist in your cluster before starting LocalStack.
@@ -24,18 +24,18 @@ You can attach custom Kubernetes labels and annotations to all child pods create
Both variables accept a comma-separated list of `key=value` pairs:
```bash
-LOCALSTACK_K8S_LABELS=env=dev,team=platform
-LOCALSTACK_K8S_ANNOTATIONS=prometheus.io/scrape=true,prometheus.io/port=8080
+K8S_LABELS=env=dev,team=platform
+K8S_ANNOTATIONS=prometheus.io/scrape=true,prometheus.io/port=8080
```
### Pod configuration
-`LOCALSTACK_K8S_POD_CONFIG` configures Kubernetes metadata, scheduling, and resource settings for child pods created by supported services such as Lambda and ECS.
+`K8S_POD_CONFIG` configures Kubernetes metadata, scheduling, and resource settings for child pods created by supported services such as Lambda and ECS.
Use it to define reusable pod profiles with fields such as `nodeSelector`, `tolerations`, `affinity`, `resources`, `labels`, and `annotations`.
The value must be valid JSON.
```bash
-LOCALSTACK_K8S_POD_CONFIG='{"profiles":{"default":{"nodeSelector":{"pool":"general"}}}}'
+K8S_POD_CONFIG='{"profiles":{"default":{"nodeSelector":{"pool":"general"}}}}'
```
For the full JSON schema, profile resolution order, and examples, see [Pod Configuration](/aws/enterprise/kubernetes/pod-configuration/).
@@ -88,10 +88,10 @@ Increase these values if your cluster is under heavy load or if image pulls are
| Variable | Description |
|---|---|
-| `LOCALSTACK_K8S_NAMESPACE` | Kubernetes namespace for child pods |
-| `LOCALSTACK_K8S_LABELS` | Comma-separated `key=value` labels applied to child pods |
-| `LOCALSTACK_K8S_ANNOTATIONS` | Comma-separated `key=value` annotations applied to child pods |
-| `LOCALSTACK_K8S_POD_CONFIG` | JSON pod configuration for supported child pods. See [Pod Configuration](/aws/enterprise/kubernetes/pod-configuration/) |
+| `K8S_NAMESPACE` | Kubernetes namespace for child pods |
+| `K8S_LABELS` | Comma-separated `key=value` labels applied to child pods |
+| `K8S_ANNOTATIONS` | Comma-separated `key=value` annotations applied to child pods |
+| `K8S_POD_CONFIG` | JSON pod configuration for supported child pods. See [Pod Configuration](/aws/enterprise/kubernetes/pod-configuration/) |
| `K8S_CONTAINER_SECURITY_CONTEXT` | JSON security context applied to child pod containers |
| `K8S_CURL_INIT_IMAGE` | Init container image used for network readiness checks |
| `LAMBDA_K8S_INIT_IMAGE` | Init container image used in Lambda pods |
diff --git a/src/content/docs/aws/enterprise/kubernetes/kubernetes-executor.md b/src/content/docs/aws/enterprise/kubernetes/kubernetes-executor.md
index 8716c4e2..422408ee 100644
--- a/src/content/docs/aws/enterprise/kubernetes/kubernetes-executor.md
+++ b/src/content/docs/aws/enterprise/kubernetes/kubernetes-executor.md
@@ -45,7 +45,7 @@ For more information, see the [Helm Chart configuration](https://github.com/loca
- Kubernetes Lambda Executor in LocalStack scales Lambda execution by spawning new environments (running in pods) during concurrent invocations.
Inactive environments shut down after 10 minutes (configurable via `LAMBDA_KEEPALIVE_MS`).
- Executor schedules multiple Lambda functions according to Kubernetes cluster defaults without specifying node affinity.
- Users can assign labels to lambda pods using the `LAMBDA_K8S_LABELS` variable (e.g., `LAMBDA_K8S_LABELS=key=value,key2=value2`).
+ Users can assign labels to lambda pods using the `K8S_LABELS` variable (e.g., `K8S_LABELS=key=value,key2=value2`).
- Timeout configurations similar to AWS are enforced using the `Timeout` function parameter.
No intrinsic limits on the number of Lambdas; default limit on concurrent executions is 1000 (`LAMBDA_LIMITS_CONCURRENT_EXECUTIONS`).
- Custom DNS configuration for Lambda on Kubernetes can be set through the `LAMBDA_DOCKER_DNS` configuration variable.
diff --git a/src/content/docs/aws/enterprise/kubernetes/pod-configuration.md b/src/content/docs/aws/enterprise/kubernetes/pod-configuration.md
index 7f32bf12..b2c05c51 100644
--- a/src/content/docs/aws/enterprise/kubernetes/pod-configuration.md
+++ b/src/content/docs/aws/enterprise/kubernetes/pod-configuration.md
@@ -12,7 +12,7 @@ tags: ["Enterprise"]
When LocalStack runs inside Kubernetes with the Kubernetes executor enabled, some services create child pods for workloads such as Lambda invocations and ECS tasks.
In heterogeneous clusters, these child pods may need additional Kubernetes configuration so they are scheduled onto the right node pools, carry the right resource requests, or integrate with cluster policies.
-Use the `LOCALSTACK_K8S_POD_CONFIG` environment variable to configure Kubernetes metadata, scheduling, and resource settings for LocalStack-spawned pods.
+Use the `K8S_POD_CONFIG` environment variable to configure Kubernetes metadata, scheduling, and resource settings for LocalStack-spawned pods.
The variable accepts a JSON object with reusable `profiles` and optional per-service mappings.
The value must be valid JSON. LocalStack validates this configuration at startup and refuses to start if the value is not valid JSON or contains unknown fields, so misconfigurations surface before any child pods are created.
@@ -77,7 +77,7 @@ To use this with the Helm chart, pass the JSON as an environment variable in you
```yaml
extraEnvVars:
- - name: LOCALSTACK_K8S_POD_CONFIG
+ - name: K8S_POD_CONFIG
value: |
{
"profiles": {
@@ -190,15 +190,15 @@ Use them only for global defaults.
Architecture values are normalized before lookup.
For example, `ARM64` is treated as `arm64`, and `x86_64` or `X86_64` are treated as `amd64`.
-The keys in `LOCALSTACK_K8S_POD_CONFIG` should still be `arm64` and `amd64`.
+The keys in `K8S_POD_CONFIG` should still be `arm64` and `amd64`.
If a service references a profile that does not exist, LocalStack logs a warning and applies no pod configuration for that request.
It does not silently fall back to `default`.
## Labels and annotations
-`labels` and `annotations` in `LOCALSTACK_K8S_POD_CONFIG` are merged into the metadata of the generated child pod.
-Profile labels override labels set through `LOCALSTACK_K8S_LABELS`, and profile annotations override annotations set through `LOCALSTACK_K8S_ANNOTATIONS`.
+`labels` and `annotations` in `K8S_POD_CONFIG` are merged into the metadata of the generated child pod.
+Profile labels override labels set through `K8S_LABELS`, and profile annotations override annotations set through `K8S_ANNOTATIONS`.
LocalStack also injects the following system labels:
@@ -257,8 +257,8 @@ The following example schedules Lambda pods on dedicated nodes, adds tolerations
## Related configuration
-- Use `LOCALSTACK_K8S_NAMESPACE` to choose the namespace for child pods.
-- Use `LOCALSTACK_K8S_LABELS` and `LOCALSTACK_K8S_ANNOTATIONS` for simple labels and annotations that apply to all child pods.
+- Use `K8S_NAMESPACE` to choose the namespace for child pods.
+- Use `K8S_LABELS` and `K8S_ANNOTATIONS` for simple labels and annotations that apply to all child pods.
- Use `K8S_CONTAINER_SECURITY_CONTEXT` to configure the security context for child pod containers.
For the complete list of Kubernetes executor configuration variables, see the [Kubernetes configuration reference](/aws/enterprise/kubernetes/configuration/).
diff --git a/src/content/docs/aws/services/ec2.mdx b/src/content/docs/aws/services/ec2.mdx
index 8669631f..41ad58ce 100644
--- a/src/content/docs/aws/services/ec2.mdx
+++ b/src/content/docs/aws/services/ec2.mdx
@@ -408,6 +408,9 @@ The token can be used in subsequent requests like so:
curl -H "x-aws-ec2-metadata-token: " -v http://169.254.169.254/latest/meta-data/
```
+You can use the [`ModifyInstanceMetadataOptions`](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_ModifyInstanceMetadataOptions.html) API to change the metadata options of a running instance, for example to require IMDSv2.
+Parameters that are omitted from the request retain their current value, matching AWS behavior.
+
:::note
IMDS IPv6 endpoint is currently not supported.
:::
@@ -688,6 +691,26 @@ Any operation not listed below will use the mock VM manager.
| `TerminateInstances` | Stops and undefines a Libvirt domain |
| `CreateVolume` | Creates a sparse Libvirt volume |
+## IAM Condition Keys
+
+When [IAM Policy Enforcement](/aws/developer-tools/security-testing/iam-policy-enforcement/) is enabled, LocalStack supports the following EC2-specific condition keys, matching the behavior described in the [AWS condition keys reference](https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazonec2.html#amazonec2-policy-keys):
+
+- `ec2:MetadataHttpTokens` — the `HttpTokens` value of an instance's [metadata options](#instance-metadata-service), useful for enforcing IMDSv2.
+- `ec2:Attribute/` — exposes request parameters (e.g. `HttpTokens` on `ModifyInstanceMetadataOptions`) as condition keys.
+
+For example, the following policy statement only allows launching instances when IMDSv2 is required:
+
+```json
+{
+ "Effect": "Allow",
+ "Action": "ec2:RunInstances",
+ "Resource": "arn:aws:ec2:*:*:instance/*",
+ "Condition": {
+ "StringEquals": { "ec2:MetadataHttpTokens": "required" }
+ }
+}
+```
+
## Resource Browser
The LocalStack Web Application provides a Resource Browser for managing EC2 instances.
diff --git a/src/content/docs/aws/services/eks.mdx b/src/content/docs/aws/services/eks.mdx
index 661d2730..ab316f0d 100644
--- a/src/content/docs/aws/services/eks.mdx
+++ b/src/content/docs/aws/services/eks.mdx
@@ -98,7 +98,7 @@ The Traefik ingress controller and the default k3d load balancer containers are
To restore the previous behavior, set the following configuration variable:
```bash
-EKS_START_K3D_LB_INGRESS=1
+K3D_START_LB_INGRESS=1
```
:::
@@ -464,7 +464,7 @@ Two AMI families are supported:
LocalStack reads the same set of fields from both formats and propagates them to the registered node, including node labels, taints, topology metadata (region, zone, instance type), and the provider ID. This matches what real EKS nodes look like from the cluster's perspective.
:::note
-Self-managed nodes use the embedded k3d-backed provider (`EKS_K8S_PROVIDER=k3s`), which is the default.
+Self-managed nodes use the embedded k3d-backed provider (`MANAGED_K8S_PROVIDER=k3s`), which is the default.
The walkthrough below runs entirely between Docker containers, so it also works on macOS where direct host-to-instance networking is not available.
:::
@@ -640,7 +640,7 @@ To try this out, follow the upstream [Getting started with Karpenter](https://ka
## Use an existing Kubernetes installation
You can also access the EKS API using your existing local Kubernetes installation.
-This can be achieved by setting the configuration variable `EKS_K8S_PROVIDER=local` and mounting the `$HOME/.kube/config` file into the LocalStack container.
+This can be achieved by setting the configuration variable `MANAGED_K8S_PROVIDER=local` and mounting the `$HOME/.kube/config` file into the LocalStack container.
When using a `docker-compose.yml` file, you need to add a bind mount like this:
```yaml
@@ -875,15 +875,15 @@ The default version is `1.35`.
| 1.31 | v1.31.14-k3s1 | eks.60 |
| 1.30 | v1.30.14-k3s2 | eks.68 |
-Users can specify the desired version when creating an EKS cluster in LocalStack using the `EKS_K3S_IMAGE_TAG` configuration variable when starting LocalStack.
+Users can specify the desired version when creating an EKS cluster in LocalStack using the `K3S_IMAGE_TAG` configuration variable when starting LocalStack.
## Configuring the k3d Cluster Token
When LocalStack creates a k3d-backed EKS cluster, it starts the k3s server with an explicit cluster token. This token is used to authenticate agent nodes joining the cluster, enabling dynamic node registration.
-By default, LocalStack uses `localstack-k3d-cluster-token` as the cluster token. You can override this value using the `EKS_K3D_CLUSTER_TOKEN` configuration variable:
+By default, LocalStack uses `localstack-k3d-cluster-token` as the cluster token. You can override this value using the `K3D_CLUSTER_TOKEN` configuration variable:
```bash
-EKS_K3D_CLUSTER_TOKEN=my-custom-token localstack start
+K3D_CLUSTER_TOKEN=my-custom-token localstack start
```
Any agent nodes added to the cluster — whether via k3d node create or k3s agent — will use the same token to authenticate with the k3s server.
diff --git a/src/content/docs/aws/services/lambda.mdx b/src/content/docs/aws/services/lambda.mdx
index 22e86312..1c068bf8 100644
--- a/src/content/docs/aws/services/lambda.mdx
+++ b/src/content/docs/aws/services/lambda.mdx
@@ -290,8 +290,8 @@ import { Table, TableHeader, TableBody, TableHead, TableRow, TableCell } from '@
🟢
🟢
🟢
- ➖
- ➖
+ 🟠
+ 🟠
BisectBatchOnFunctionError
@@ -305,9 +305,9 @@ import { Table, TableHeader, TableBody, TableHead, TableRow, TableCell } from '@
ScalingConfig
- The scaling configuration for the event source.
- 🟠
- 🟠
+ The scaling configuration for the event source. [^3]
+ 🟡
+ 🟡
➖
➖
➖
@@ -375,9 +375,9 @@ import { Table, TableHeader, TableBody, TableHead, TableRow, TableCell } from '@
ProvisionedPollerConfig
- Control throughput via min-max limits.
- ➖
- ➖
+ Control throughput via min-max limits. [^4]
+ 🟡
+ 🟡
➖
➖
🟠
@@ -428,6 +428,8 @@ import { Table, TableHeader, TableBody, TableHead, TableRow, TableCell } from '@
[^1]: Read more at [Control which events Lambda sends to your function](https://docs.aws.amazon.com/lambda/latest/dg/invocation-eventfiltering.html)
[^2]: The available Metadata properties may not have full parity with AWS depending on the event source (read more at [Understanding event filtering basics](https://docs.aws.amazon.com/lambda/latest/dg/invocation-eventfiltering.html#filtering-basics)).
+[^3]: For SQS Standard, `ScalingConfig` is accepted but not automatically applied — the poller count is currently hard-coded rather than scaled based on `MaximumConcurrency`. For SQS FIFO, a single poller is always used to preserve message-group ordering, so the scaling configuration is accepted but has no effect.
+[^4]: For SQS Standard, only `MinimumPollers` is honored (it sets the poller count); `MaximumPollers` has no effect. For SQS FIFO, the single-poller model means the configuration is accepted but ignored.
Create a [GitHub Discussion](https://github.com/orgs/localstack/discussions/new/choose) or reach out to [LocalStack Support](/aws/help-support/get-help/) if you experience any challenges.
]