Skip to content

Commit 8568cf4

Browse files
committed
feat: allow overriding builder image or base
1 parent 4a1c8f2 commit 8568cf4

7 files changed

Lines changed: 268 additions & 32 deletions

File tree

API.md

Lines changed: 52 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,5 +106,44 @@ PythonFunction(
106106
## Notes
107107

108108
- `index` can be passed as `handler.py` or `handler`.
109-
- Use `bundling` to pass Docker environment variables, asset excludes, build args, or command hooks.
109+
- Use `bundling` to pass Docker environment variables, asset excludes, build args, command hooks, or a custom builder image.
110+
- Set `bundling.buildArgs.BUNDLING_IMAGE` to swap the Python base image used by the default builder.
111+
- Set `bundling.image` to provide a fully custom builder image.
110112
- See [API.md](API.md) for the full API reference.
113+
114+
## Customizing The Builder Image
115+
116+
If you want to keep the default `uv-python-lambda` builder logic but use a different Python base image, override `BUNDLING_IMAGE`:
117+
118+
```ts
119+
new PythonFunction(this, 'Fn', {
120+
rootDir: path.join(__dirname, '..', '..', 'services', 'fetcher'),
121+
bundling: {
122+
buildArgs: {
123+
BUNDLING_IMAGE: 'python:3.12-slim',
124+
},
125+
},
126+
});
127+
```
128+
129+
When you override `BUNDLING_IMAGE`, the library still uses its own default
130+
builder scripts. Those scripts need `bash`, `rsync`, and a few standard Unix
131+
utilities, so the default builder image now installs them on top of the chosen
132+
base image. The conditional `RUN if command -v ...` block in
133+
`resources/Dockerfile` exists to do that across common Debian, RPM, and
134+
Alpine-based images.
135+
136+
If you need full control over the builder container, pass `bundling.image` instead. Custom images must include Python, `uv`, and the `/opt/uv-python-lambda` scripts expected by this library.
137+
138+
```ts
139+
import { DockerImage } from 'aws-cdk-lib';
140+
141+
new PythonFunction(this, 'Fn', {
142+
rootDir: path.join(__dirname, '..', '..', 'services', 'fetcher'),
143+
bundling: {
144+
image: DockerImage.fromBuild(path.join(__dirname, '..', '..'), {
145+
file: 'docker/uv-python-lambda-builder.Dockerfile',
146+
}),
147+
},
148+
});
149+
```

resources/Dockerfile

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,40 @@
11
# Use for the container image that uv-python-lambda will use to bundle one or more Lambda functions
22
# from a uv project (including those with one or more workspaces).
33

4-
ARG IMAGE_ARCH=arm64 # or x86_64 (not amd64 as per Docker platform)
54
ARG UV_VERSION=0.5.27
6-
ARG PYTHON_VERSION=3.12
7-
ARG BUNDLING_IMAGE=public.ecr.aws/sam/build-python${PYTHON_VERSION}:latest-${IMAGE_ARCH}
5+
ARG BUNDLING_IMAGE=public.ecr.aws/sam/build-python3.12:latest-arm64
86

97
FROM ghcr.io/astral-sh/uv:${UV_VERSION} AS uv
108
FROM ${BUNDLING_IMAGE}
119

10+
# The default builder scripts depend on a few basic Unix tools that are present
11+
# in the SAM build images but may be missing from slimmer custom base images
12+
# such as `python:3.12-slim`.
13+
# - `bash` is required because `entrypoint.sh` and `export.sh` are bash scripts.
14+
# - `coreutils` / `util-linux` provide commands used by the scripts such as
15+
# `realpath`, `mktemp`, and `flock`-adjacent utilities across distros.
16+
# - `rsync` is used to stage workspace and source files efficiently.
17+
#
18+
# We detect the distro package manager here so `BUNDLING_IMAGE` can point at a
19+
# broader range of Debian, RPM, or Alpine-based Python images without requiring
20+
# users to maintain a separate custom builder image just to install these tools.
21+
RUN if command -v apt-get >/dev/null 2>&1; then \
22+
apt-get update && \
23+
apt-get install -y --no-install-recommends bash coreutils rsync util-linux && \
24+
rm -rf /var/lib/apt/lists/*; \
25+
elif command -v dnf >/dev/null 2>&1; then \
26+
dnf install -y bash coreutils rsync util-linux && \
27+
dnf clean all; \
28+
elif command -v yum >/dev/null 2>&1; then \
29+
yum install -y bash coreutils rsync util-linux && \
30+
yum clean all; \
31+
elif command -v apk >/dev/null 2>&1; then \
32+
apk add --no-cache bash coreutils rsync util-linux; \
33+
else \
34+
echo "Unsupported base image: install bash, coreutils, rsync, and util-linux in the custom BUNDLING_IMAGE" >&2; \
35+
exit 1; \
36+
fi
37+
1238
COPY --from=uv /uv /uvx /bin/
1339
COPY *.sh /opt/uv-python-lambda/
1440
RUN chmod +x /opt/uv-python-lambda/*.sh

src/bundling.ts

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,13 @@ export class Bundling {
157157
const hashableProperties = {
158158
runtime: props.runtime.name,
159159
architecture: props.architecture ?? Architecture.ARM_64,
160-
buildArgs: props.buildArgs,
161160
environment: getBuilderEnvironment(props.environment),
162161
rootDir: path.resolve(props.rootDir),
163-
uvVersion: props.uvVersion ?? DEFAULT_UV_VERSION,
162+
builderImage: props.image
163+
? { image: props.image.image }
164+
: {
165+
buildArgs: this.getDefaultBuilderBuildArgs(),
166+
},
164167
};
165168

166169
this.containerBuilderKey = `uv-bundling-${hash(hashableProperties)}`;
@@ -272,24 +275,13 @@ export class Bundling {
272275
return existing;
273276
}
274277

275-
const buildImage = DockerImage.fromBuild(
276-
path.resolve(__dirname, '..', 'resources'),
277-
{
278-
buildArgs: {
279-
...this.props.buildArgs,
280-
UV_VERSION: this.props.uvVersion ?? DEFAULT_UV_VERSION,
281-
IMAGE: this.props.runtime.bundlingImage.image,
282-
IMAGE_ARCH:
283-
this.props.architecture === Architecture.X86_64
284-
? 'x86_64'
285-
: 'arm64',
286-
PYTHON_VERSION: this.props.runtime.name.slice(6),
287-
BUNDLING_IMAGE: this.props.runtime.bundlingImage.image,
288-
},
278+
const buildImage =
279+
this.props.image ??
280+
DockerImage.fromBuild(path.resolve(__dirname, '..', 'resources'), {
281+
buildArgs: this.getDefaultBuilderBuildArgs(),
289282
platform: (this.props.architecture ?? Architecture.ARM_64)
290283
.dockerPlatform,
291-
},
292-
);
284+
});
293285

294286
Bundling.buildImages[imageKey] = buildImage;
295287
return buildImage;
@@ -315,6 +307,16 @@ export class Bundling {
315307
([a], [b]) => a.localeCompare(b),
316308
);
317309
}
310+
311+
private getDefaultBuilderBuildArgs(): Record<string, string> {
312+
return {
313+
...this.props.buildArgs,
314+
UV_VERSION: this.props.uvVersion ?? DEFAULT_UV_VERSION,
315+
BUNDLING_IMAGE:
316+
this.props.buildArgs?.BUNDLING_IMAGE ??
317+
this.props.runtime.bundlingImage.image,
318+
};
319+
}
318320
}
319321

320322
function encodeCommands(commands: string[]) {

src/types.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type {
22
AssetHashType,
33
BundlingFileAccess,
4+
DockerImage,
45
DockerRunOptions,
56
} from 'aws-cdk-lib/core';
67

@@ -33,8 +34,23 @@ export interface BundlingOptions extends DockerRunOptions {
3334
readonly outputPathSuffix?: string;
3435

3536
/**
36-
* Optional build arguments to pass to the default container. This can be used to customize
37-
* the index URLs used for installing dependencies.
37+
* Custom builder image to use for bundling.
38+
*
39+
* Use this for full control over the bundling environment. The image must
40+
* include Python, `uv`, and the `/opt/uv-python-lambda` scripts expected by
41+
* this library.
42+
*
43+
* To customize only the base image used by the default builder, prefer
44+
* `buildArgs.BUNDLING_IMAGE`.
45+
*
46+
* @default - Build the library default builder image from `resources/`
47+
*/
48+
readonly image?: DockerImage;
49+
50+
/**
51+
* Optional build arguments to pass to the default builder image. This can be
52+
* used to customize the index URLs used for installing dependencies, or to
53+
* override `BUNDLING_IMAGE` with a different Python base image.
3854
* This is not used if a custom image is provided.
3955
*
4056
* @default - No build arguments.

0 commit comments

Comments
 (0)