skip to content

In AWS Lambda, what does attaching a layer actually do to the function's execution environment, and what limits and gotchas come with layers?

level: middleimportance: should knowfreq 55%

answer

  1. extracted into /opt before init
  2. the runtime already searches those paths
  3. five per function
  4. still inside the 250 MB budget
  5. versions are immutable, pinned by ARN

basics

~20 s

A layer is a .zip of shared content that Lambda extracts into /opt before the handler runs, where each runtime already looks for libraries. A function may attach up to five layer versions, and their contents still count against the 250 MB unzipped limit.

solid answer

~50 s

A layer is just a versioned .zip archive published separately from any function. When a function that references it initialises, Lambda extracts the layer's contents into `/opt` in the execution environment, and each managed runtime already searches conventional paths under there — `/opt/python` for Python, `/opt/nodejs/node_modules` for Node.js, `/opt/java/lib` for the JVM — while `/opt/bin` is on `PATH` and `/opt/lib` on `LD_LIBRARY_PATH`, which is how people ship native binaries. The limits: **five layers per function**, and the combined unzipped size of the function plus all its layers must still fit in **250 MB**. Layer versions are immutable and referenced by an ARN ending in a version number, so publishing an update does *not* propagate — every consuming function must be redeployed against the new ARN. Layers are also unavailable to container-image functions. I use them to share a common utility or an AWS-provided extension across many small functions, not as a dependency manager: there is no transitive resolution and no conflict detection.

code

bash · 15 lines
bash
# The directory prefix inside the archive is what makes the layer importable
mkdir -p python
pip install requests -t python/
zip -r layer.zip python/
unzip -l layer.zip | head   # expect paths like python/requests/__init__.py

ARN=$(aws lambda publish-layer-version \
  --layer-name shared-http \
  --zip-file fileb://layer.zip \
  --compatible-runtimes python3.12 \
  --query LayerVersionArn --output text)

# Functions pin an exact version ARN; there is no "latest"
aws lambda update-function-configuration \
  --function-name my-func --layers "$ARN"

go deeper

for a junior

Know that a layer is shared, separately published content that Lambda unpacks into /opt so several functions can use one copy of a library, and that a function can attach at most five.

for a middle

Explain the per-runtime directory prefixes that make a layer importable, state that the combined unzipped size still has to fit 250 MB, and describe layer-version immutability and the ARN pinning that comes with it.

for a senior

Weigh layers against just vendoring dependencies: who owns the shared layer, how a security patch reaches every consumer, what native-binary layers require in terms of OS and architecture, and how you keep two layers from colliding on the same path.

for a principal

Decide whether a shared-layer strategy is worth its coordination cost at your scale, define ownership and update SLAs for platform-published layers, and set the cross-account or organization sharing policy including whether anything may be published publicly.

## What a layer is A Lambda layer is a .zip archive of libraries, a custom runtime, configuration files or executables that you publish as its own resource, independent of any function. A function then references specific **layer versions** by ARN. At most five. The mechanism is deliberately dumb, which is the key to reasoning about it: when Lambda creates an execution environment for a function, it extracts each referenced layer into the `/opt` directory before your initialization code runs. That is the whole feature. Everything else follows from where the runtime happens to look. ## Why /opt makes the libraries visible Each managed runtime is preconfigured with `/opt` paths on its search path, so a correctly structured layer needs no configuration at all: | Runtime | Path inside the layer .zip | |---|---| | Python | `python/` or `python/lib/python3.12/site-packages/` | | Node.js | `nodejs/node_modules/` | | Java | `java/lib/` | | Ruby | `ruby/lib/` | | Any (executables) | `bin/` — ends up on `PATH` | | Any (shared objects) | `lib/` — ends up on `LD_LIBRARY_PATH` | Getting this structure wrong is the single most common layer bug: a layer built by zipping `site-packages` directly, without the `python/` prefix, extracts to `/opt/<package>` and the import fails. The fix is always to check the archive listing before publishing. ```bash pip install requests -t python/ zip -r layer.zip python/ aws lambda publish-layer-version --layer-name shared-http \ --zip-file fileb://layer.zip --compatible-runtimes python3.12 ``` The `bin/` and `lib/` rows are the interesting ones: a layer is how you get a static `ffmpeg` binary or a compiled shared object into a zip-packaged function. Those binaries must be built for the runtime's operating system and CPU architecture — Amazon Linux for the managed runtimes, and `x86_64` versus `arm64` matters — which is why native layers are usually built inside a matching container. ## The limits - **Five layer versions per function.** A hard cap, not a soft one. - **250 MB unzipped, combined.** Function code plus every attached layer, measured after extraction. This is the limit people misunderstand most: layers do not enlarge the budget, they only move where the bytes are stored so the deployed artifact stays small and several functions can share one copy. - **Not available to container-image functions.** With an image you bake the same content in. - **Merge order matters.** Layers are extracted in the order listed on the function, so when two layers contain the same path, the later one wins. Relying on that is fragile; avoid overlapping layers. ## Immutability and the update problem Publishing a layer creates a new, immutable **layer version** with a monotonically increasing number, and functions reference `arn:aws:lambda:<region>:<account>:layer:<name>:<version>`. There is no "latest" alias. Republishing a fixed dependency therefore does *not* patch anything: every function referencing the old version keeps running the old bytes until someone updates its configuration and redeploys. This cuts both ways. It is a genuine safety property — nobody can change what your function loads out from under you, and deleting a layer version does not break functions already using it (though no new function can then attach it). It is also the operational cost of layers: a security fix in a shared layer becomes a fan-out task across every consumer, and without automation the fleet drifts. Teams that adopt layers heavily usually end up building exactly the tracking they were trying to avoid. ## Sharing across accounts A layer version carries a resource policy. `AddLayerVersionPermission` grants `lambda:GetLayerVersion` to another account, to an entire AWS Organization via the organization ID, or to everyone (`*`) for a public layer. That is how AWS itself distributes things like its parameter-and-secret caching extension, and how a platform team publishes a shared runtime helper once for all workload accounts. Treat making a layer public as a deliberate decision — you can't unpublish bytes people have already pulled. ## When a layer is the right tool — and when it isn't Good fits: an AWS-provided or vendor extension; a shared client wrapper, logging configuration or tracing bootstrap used by dozens of small functions; a native binary that would otherwise be duplicated in every artifact; a custom runtime. Poor fits: a layer used as a package manager. There is no dependency resolution, no version constraint solving, and no detection of a conflict between a library in a layer and the same library in the function package — the runtime simply finds whichever comes first on its search path, which produces mystifying behaviour. If your build tool can already vendor dependencies reproducibly into the artifact, and the artifact fits, that is usually the simpler answer.

  • You publish a patched version of a shared layer. What happens to the functions already using it?
    Nothing — they keep loading the old layer version, because a function pins an exact version ARN and there is no floating alias. Rolling out the patch means updating each consuming function's configuration and redeploying it. That immutability is a safety property, but it makes fleet-wide dependency updates a tracked task rather than a single publish.
  • A Python layer builds fine but every function using it fails on import. What do you check first?
    The directory structure inside the archive. Python content must sit under `python/` (or `python/lib/python3.x/site-packages/`) so it extracts to a path the runtime searches under `/opt`; zipping `site-packages` contents directly puts the packages one level too high. `unzip -l` on the layer archive answers it in one command.
  • How would you share a layer with other AWS accounts in your organization?
    Attach a resource policy to the layer version with `AddLayerVersionPermission`, granting `lambda:GetLayerVersion` to a specific account or to the whole organization via its organization ID. Consumers then reference the full layer version ARN from their own accounts. Layers are Regional, so a multi-Region fleet needs the layer published in each Region.

saying these in an interview costs you the question

  • Thinks a layer increases the 250 MB unzipped limit
  • Believes functions pick up new layer versions automatically
  • Zips the package contents without the runtime's directory prefix
  • Uses layers as a dependency manager expecting conflict resolution
  • Tries to attach a layer to a container-image function

context