skip to content

AWS Lambda lets you deploy a function either as a .zip archive or as a container image. What are the practical differences between the two packaging formats, and how would you choose one for a given function?

level: middleimportance: must knowfreq 62%

answer

  1. same execution model, different envelope
  2. 250 MB unzipped is the real ceiling
  3. layers count against that budget
  4. ten gigabytes, but from private ECR
  5. images cannot use layers

basics

~20 s

Both run on the same Lambda execution model; only packaging differs. A .zip is small, quick to deploy and can use layers, but is capped at 250 MB unzipped. A container image allows up to 10 GB from a private ECR repository and carries OS-level dependencies, at the cost of an image build pipeline.

solid answer

~60 s

The runtime behaviour is identical — same memory and timeout settings, same billing, same event sources — so the choice is about size, dependencies and build workflow. A **.zip archive** is capped at 50 MB when uploaded directly and 250 MB unzipped, counting any layers attached; it deploys in seconds, supports layers, and the smallest ones are editable in the console. A **container image** may be up to 10 GB, must live in a private Amazon ECR repository in the same Region as the function, and must implement the Lambda Runtime API — AWS base images already include the runtime interface client, or you add it yourself. Images cannot use layers, and the image's `CMD` supplies the handler. I reach for .zip by default because it is simpler and smaller, and switch to an image when the function genuinely needs more than 250 MB or needs OS-level binaries — large native ML wheels, `ffmpeg`, a headless browser — or when the organisation already standardises on image builds and registry scanning.

code

dockerfile · 9 lines
dockerfile
FROM public.ecr.aws/lambda/python:3.12

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt -t "${LAMBDA_TASK_ROOT}"

COPY app.py "${LAMBDA_TASK_ROOT}"

# CMD is the handler for an image-packaged Lambda function
CMD ["app.lambda_handler"]

go deeper

for a junior

Know that a Lambda function ships either as a .zip of your code and dependencies or as a container image, and that the zip route has a much smaller size ceiling.

for a middle

Give the numbers and the mechanics: the unzipped ceiling counts layers, images come from private ECR in the same Region, images supply the handler through CMD and cannot use layers, and both formats execute identically.

for a senior

Justify a choice for a real workload, name the operational costs of images — registry lifecycle, base-image patching, scanning, rollback that depends on retained digests — and describe what changing format does to the build pipeline.

for a principal

Set the org-wide default and the escape hatch: which format teams start with, when a function is allowed to migrate, how base images are owned and patched, and how artifact retention is aligned with the rollback guarantees you promise.

## The same function, two envelopes A Lambda function's packaging format decides how your code and dependencies reach the service. It does *not* change the execution model: an image-packaged function has the same memory range, the same 15-minute maximum timeout, the same `/tmp`, the same event sources and the same per-GB-second billing as a zip-packaged one. So the choice is engineering logistics, not capability — except for one hard axis, size. ## .zip archives A .zip contains your handler file plus every dependency it imports, laid out from the archive root (`pip install -t .`, `npm install --production`, a Gradle shadow jar, and so on). The quotas that matter, as published by AWS in 2025: - **50 MB** for a direct upload through the API or console. Larger archives must be staged in S3 and referenced by bucket and key. - **250 MB** unzipped — and this is the ceiling that actually bites, because it counts your code *plus the contents of every attached layer*. - **3 MB** for a function still editable in the console's inline editor. In exchange you get the fastest possible loop: build the archive, `UpdateFunctionCode`, done in seconds. Layers are available, so shared dependencies can be published once and attached to many functions. ## Container images An image-packaged function is an OCI image that implements the **Lambda Runtime API** — the HTTP contract the service uses to hand an event to your process and take the response back. AWS publishes base images per runtime that already bundle the *runtime interface client* implementing that contract; you can also start from your own base and add the client library yourself. ```dockerfile FROM public.ecr.aws/lambda/python:3.12 COPY requirements.txt ./ RUN pip install -r requirements.txt -t "${LAMBDA_TASK_ROOT}" COPY app.py "${LAMBDA_TASK_ROOT}" CMD ["app.lambda_handler"] ``` The rules to remember: - Maximum image size is **10 GB** — forty times the zip ceiling. - The image must be pushed to a **private Amazon ECR repository in the same Region** as the function. - The image's `CMD` supplies the handler; there is no separate handler configuration to keep in sync. - **Layers do not apply.** Whatever a layer would have provided, you bake into the image instead — which is arguably cleaner, since it is one artifact rather than an artifact plus five pinned layer ARNs. - Deployment references an **image digest**, so a function version pins the exact image content, not a mutable tag. ## What actually drives the decision **Size and native dependencies.** This is the decisive one. Media processing with `ffmpeg`, headless Chromium for PDF rendering, PyTorch or a large data-science stack — these blow past 250 MB unzipped before you have written a line of business logic. Nothing about a zip fixes that; move to an image. **Build and supply-chain workflow.** If everything else in the organisation is a container — the same registry, the same base-image patching, the same vulnerability scanning, the same signing — then packaging Lambda functions the same way removes a special case. That consistency argument is legitimate even when the function is 8 MB. **Iteration speed and simplicity.** A small zip is faster to build, faster to push, easier to inspect (`unzip -l`), and needs no registry, no ECR lifecycle policy, and no image-cleanup job. For a glue function that stitches two AWS services together, an image is overhead. **Start-up behaviour.** Lambda optimises and caches images so that a large image is not proportionally slower to start than a small one — but a bloated image with hundreds of megabytes of unused layers is still worth trimming. Multi-stage builds and installing only production dependencies matter here exactly as they do anywhere else. **Local testing.** With an image you can run the function locally against the same runtime interface, which some teams value highly for debugging. ## Things candidates get wrong The most common error is believing an image-packaged function is somehow "less serverless" — that it gets a different scaling model, or a long-lived container, or different pricing. It does not; Lambda extracts and runs the image inside the same execution environment. The second is assuming a layer can push a zip past 250 MB. Layers help you *share* dependencies across functions and keep the deployed artifact small; the unzipped ceiling is measured after layers are merged in, so a fat dependency in a layer consumes exactly the same budget. The third is switching format casually. Moving from zip to image changes your build, your CI permissions (ECR push), your rollback story and your artifact retention policy. It is a deliberate migration, not a checkbox.

  • A team says they will attach a layer to get past the 250 MB unzipped limit. What do you tell them?
    It won't work — the 250 MB limit is measured on the function code and all attached layers after extraction, so moving a fat dependency into a layer changes nothing. Layers reduce the size of the uploaded artifact and let several functions share one copy; they do not raise the ceiling. Genuinely exceeding 250 MB means packaging as a container image.
  • How do you roll back an image-packaged function after a bad deploy?
    Publish a version for each deploy and point an alias at it; a rollback is then moving the alias back to the previous version number. Versions pin the image digest, not a mutable tag, so the old version keeps running exactly the image it was published with even if someone re-pushes the same tag. Keep those images in ECR — a lifecycle policy that deletes them breaks the rollback.
  • Does packaging as a container image change how the function scales or how you are billed?
    No. Scaling, concurrency, event sources, the maximum timeout and per-GB-second billing are identical for both formats. The differences are the size ceiling, the ECR requirement, the loss of layers, and the fact that the handler comes from the image's CMD. Anyone who claims image functions behave like long-lived containers has the model wrong.
  • What must a custom base image provide for Lambda to run it?
    It must implement the Lambda Runtime API — the HTTP loop that fetches the next event and posts the response. AWS base images bundle a runtime interface client that does this; on your own base you add the client library for your language. The image also has to be in a private ECR repository in the function's Region and be built for the function's architecture.

saying these in an interview costs you the question

  • Thinks a layer raises the 250 MB unzipped limit
  • Believes image-packaged functions scale or bill differently
  • Assumes an image can be pulled from Docker Hub
  • Says images are always slower and never worth it
  • Cannot state the size ceiling for either format

context