Skip to content

Commit 94cd0ca

Browse files
authored
Merge pull request #6 from anysphere/cursor/byo-ecr-microvm-image-5528
Document BYO ECR images as the MicroVM container base
2 parents 6e88a3d + 2eb3439 commit 94cd0ca

5 files changed

Lines changed: 160 additions & 1 deletion

File tree

‎.github/workflows/ci.yml‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ jobs:
1818
test -f controller/Dockerfile
1919
test -f controller/handler.py
2020
test -f microvm-image/Dockerfile
21+
test -f microvm-image/Dockerfile.ecr
2122
test -f microvm-image/entrypoint.sh
2223
test -f microvm-image/hook.py
2324
test ! -f handler.mjs
@@ -39,3 +40,10 @@ jobs:
3940
grep -q 'endswith("/validate")' microvm-image/hook.py
4041
grep -q 'endswith("/run")' microvm-image/hook.py
4142
grep -q '"validate":"ENABLED"' README.md
43+
grep -q "Bring your own ECR image" README.md
44+
grep -q "ecr:GetAuthorizationToken" cloudformation.yaml
45+
grep -q "ecr:BatchCheckLayerAvailability" cloudformation.yaml
46+
grep -q "ecr:GetDownloadUrlForLayer" cloudformation.yaml
47+
grep -q "ecr:BatchGetImage" cloudformation.yaml
48+
grep -q "COPY entrypoint.sh hook.py" microvm-image/Dockerfile.ecr
49+
grep -q 'ENTRYPOINT \["python3", "/opt/cursor/hook.py"\]' microvm-image/Dockerfile.ecr

‎README.md‎

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ Users pick `octocat/Hello-World` in the dashboard (the pool appears under that r
9696

9797
`https://github.com/octocat/Hello-World` (`octocat/Hello-World`) is a public sample so you can clone without configuring git credentials. Replace it with your real repository before you run real work. Private repos need git auth (HTTPS token or SSH) on the worker.
9898

99-
3. Build the worker image from [`microvm-image/`](microvm-image/) (needs the stack outputs). Enable both `ready` and `validate` image hooks. After this template change, **rebuild the MicroVM image** so those hooks are in the snapshot path:
99+
3. Build the worker image from [`microvm-image/`](microvm-image/) (needs the stack outputs). Enable both `ready` and `validate` image hooks. After this template change, **rebuild the MicroVM image** so those hooks are in the snapshot path. The stock Dockerfile starts from `public.ecr.aws/lambda/microvms:al2023-minimal`. To start from an application image you already publish to ECR, see [Bring your own ECR image](#bring-your-own-ecr-image).
100100

101101
```bash
102102
BUCKET=$(aws cloudformation describe-stacks --stack-name cursor-lambda-workers \
@@ -117,6 +117,65 @@ Users pick `octocat/Hello-World` in the dashboard (the pool appears under that r
117117

118118
4. Start an agent from [cursor.com/agents](https://cursor.com/agents) against the pool, or against `octocat/Hello-World` for the repo-bound walkthrough. That GitHub repo is a public sample so you can clone without configuring git credentials. Replace it with your real repository before you run real work.
119119

120+
## Bring your own ECR image
121+
122+
`create-microvm-image` still takes `--code-artifact uri=s3://.../app.zip` (a zip whose root contains a `Dockerfile` plus app artifacts) and `--base-image-arn` (a Lambda-managed MicroVM OS from `list-managed-microvm-images`). Your ECR image is the **container** base via `FROM` in that Dockerfile, not an argument to `--code-artifact` or `--base-image-arn`. Lambda builds the Dockerfile inside the managed OS, then snapshots the result. Official docs: [Container base images / Using a private ECR image](https://docs.aws.amazon.com/lambda/latest/dg/microvms-images.html).
123+
124+
Keep publishing the application image from CI/CD as you already do (deps, toolchain, repo-specific packages). The MicroVM zip is thin: a Dockerfile that `FROM`s that image (tag or digest) plus this repo’s Cursor worker files. Rebuilding the MicroVM image is what picks up a new CI image. `run-microvm` still uses the MicroVM image name or ARN (`cursor-pool-worker` here), not the ECR URI.
125+
126+
### Dockerfile
127+
128+
Use [`microvm-image/Dockerfile.ecr`](microvm-image/Dockerfile.ecr). Set `FROM` to your image, then layer the same worker bits the stock image installs. Do not drop them: snapshot and smoke-test still POST `/ready` and `/validate` on port 9000.
129+
130+
```dockerfile
131+
FROM 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-ci-image:tag
132+
# linux/arm64 (this template’s guests are aarch64). Linux, snapshot-compatible.
133+
# Image must be reachable from Lambda build (public internet or ECR in this account).
134+
# Ensure git, python3, curl, tar, and awscli if the CI image does not already
135+
# have them (package manager depends on FROM).
136+
137+
COPY cursor-agent-version /tmp/cursor-agent-version
138+
# install cursor-agent for linux/arm64 (same RUN as the stock Dockerfile)
139+
140+
COPY entrypoint.sh hook.py /opt/cursor/
141+
RUN chmod +x /opt/cursor/entrypoint.sh /opt/cursor/hook.py && mkdir -p /opt/cursor/workspaces
142+
ENV HOOK_PORT=9000
143+
ENTRYPOINT ["python3", "/opt/cursor/hook.py"]
144+
```
145+
146+
The guest still needs:
147+
148+
- Cursor agent CLI (`cursor-agent` / `agent worker … start`)
149+
- git (repo-bound clone in [`entrypoint.sh`](microvm-image/entrypoint.sh))
150+
- `/run`, `/ready`, `/validate` on port 9000 ([`hook.py`](microvm-image/hook.py))
151+
- entrypoint that starts the worker with `CURSOR_*` on `/run`
152+
153+
Zip `Dockerfile.ecr` as `Dockerfile` so you do not overwrite the stock quickstart:
154+
155+
```bash
156+
rm -f /tmp/app.zip
157+
TMP=$(mktemp -d)
158+
cp microvm-image/entrypoint.sh microvm-image/hook.py microvm-image/cursor-agent-version "$TMP/"
159+
cp microvm-image/Dockerfile.ecr "$TMP/Dockerfile"
160+
( cd "$TMP" && zip -r /tmp/app.zip . )
161+
aws s3 cp /tmp/app.zip "s3://${BUCKET}/app.zip"
162+
```
163+
164+
Then the same `create-microvm-image` as Deploy step 3: same `--base-image-arn` from `list-managed-microvm-images`, `--build-role-arn`, and `--hooks` with `ready` and `validate` **ENABLED**. Keep those hooks; BYO ECR does not change the snapshot path.
165+
166+
### IAM (private ECR)
167+
168+
The MicroVM **build role** must pull `FROM`. [`cloudformation.yaml`](cloudformation.yaml) `BuildRole` includes:
169+
170+
- `ecr:GetAuthorizationToken` (`Resource: *` — that action does not support resource-level IAM)
171+
- `ecr:BatchCheckLayerAvailability`, `ecr:GetDownloadUrlForLayer`, `ecr:BatchGetImage` on this account’s ECR repositories
172+
173+
Redeploy the stack so those statements exist before the first BYO build. Cross-account ECR needs extra policy on the role and a repository policy on the other account; this template does not add that.
174+
175+
### Architecture
176+
177+
This template’s MicroVM guests are **aarch64**. The ECR image must be `linux/arm64`. An amd64-only CI image fails the MicroVM build or fails at runtime (`Exec format error` on `node`). Publish an arm64 or multi-arch tag from CI.
178+
120179
## Run a cloud agent
121180

122181
Open [cursor.com/agents](https://cursor.com/agents). Choose **Self-hosted**.
@@ -155,6 +214,7 @@ aws lambda-microvms list-microvms --image-identifier cursor-pool-worker
155214
| Symptom | What to check |
156215
| --- | --- |
157216
| Image build fails (S3 or IAM) | Confirm stack outputs `ArtifactBucketName` and `BuildRoleArn`. The zip must land in that bucket, and the build role must be able to read it. |
217+
| Image build fails pulling `FROM` (ECR) | Do not pass an ECR URI to `--code-artifact` or `--base-image-arn`. Put the URI in the zip’s `Dockerfile` `FROM`. Redeploy so `BuildRole` can pull private ECR. Image must be Linux `linux/arm64`, snapshot-compatible, and in this account (or public). |
158218
| Image built before `/ready`+`/validate` | Rebuild the MicroVM image so those hooks are in the snapshot path. Existing snapshots were taken without them. |
159219
| No MicroVM | Confirm the controller is running and can call `run-microvm`. For a local controller, assume `SpawnRoleArn`. Confirm image `cursor-pool-worker` exists. |
160220
| Worker dies immediately | The guest needs `CURSOR_API_KEY` (SSM `/cursor-lambda-workers/cursor-api-key`). Confirm the `/run` hook started `cursor-agent worker --pool … start`. If logs show `Exec format error` on `node`, the image installed the wrong CLI arch (MicroVMs here are aarch64). If auth says the API key is invalid, do not set `CURSOR_API_ENDPOINT` to `https://api.cursor.com`. |
@@ -163,6 +223,7 @@ aws lambda-microvms list-microvms --image-identifier cursor-pool-worker
163223
## Related resources
164224

165225
- [AWS Lambda MicroVMs](https://docs.aws.amazon.com/lambda/latest/dg/lambda-microvms-guide.html)
226+
- [MicroVM images (container base / private ECR)](https://docs.aws.amazon.com/lambda/latest/dg/microvms-images.html)
166227
- [Cursor self-hosted pools](https://cursor.com/docs/cloud-agent/self-hosted-guides/pool.md) ([Any repo / repo-less](https://cursor.com/docs/cloud-agent/self-hosted-guides/pool.md#repo-less-pools), [pool names](https://cursor.com/docs/cloud-agent/self-hosted-guides/pool.md#pool-names), [multiple repo roots](https://cursor.com/docs/cloud-agent/self-hosted-guides/pool.md#register-multiple-repo-roots))
167228
- This repo: [`spawn.sh`](spawn.sh), [`cloudformation.yaml`](cloudformation.yaml), [`microvm-image/`](microvm-image/)
168229

‎cloudformation.yaml‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,17 @@ Resources:
105105
- Effect: Allow
106106
Action: ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"]
107107
Resource: !Sub "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/microvms/*"
108+
# Private ECR FROM during create-microvm-image. GetAuthorizationToken
109+
# does not support resource-level IAM (same as AWS's sample).
110+
- Effect: Allow
111+
Action: ecr:GetAuthorizationToken
112+
Resource: "*"
113+
- Effect: Allow
114+
Action:
115+
- ecr:BatchCheckLayerAvailability
116+
- ecr:GetDownloadUrlForLayer
117+
- ecr:BatchGetImage
118+
Resource: !Sub "arn:${AWS::Partition}:ecr:*:${AWS::AccountId}:repository/*"
108119

109120
SpawnRole:
110121
Type: AWS::IAM::Role

‎microvm-image/Dockerfile‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
ARG BASE_IMAGE=public.ecr.aws/lambda/microvms:al2023-minimal
22
FROM ${BASE_IMAGE}
3+
# To FROM a private ECR application image instead, see Dockerfile.ecr and the
4+
# README section "Bring your own ECR image". --base-image-arn stays a
5+
# Lambda-managed OS from list-managed-microvm-images.
36

47
RUN dnf install -y --setopt=install_weak_deps=0 \
58
bash tar gzip git ca-certificates findutils awscli python3 \

‎microvm-image/Dockerfile.ecr‎

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Example: layer the Cursor worker runtime on an application image you already
2+
# publish to ECR from CI/CD. This is not the default; the stock Dockerfile
3+
# remains the quickstart.
4+
#
5+
# create-microvm-image does not take an ECR URI as the MicroVM image.
6+
# --code-artifact is a zip with a Dockerfile (this file, named Dockerfile in
7+
# the zip) plus Cursor files. --base-image-arn is a Lambda-managed MicroVM OS
8+
# from `list-managed-microvm-images`. Your ECR image is only the container
9+
# FROM below. See README "Bring your own ECR image".
10+
#
11+
# Edit FROM to your image tag or digest. create-microvm-image may not pass
12+
# --build-arg; do not rely on ARG for the image URI.
13+
#
14+
# Keep this file in sync with Dockerfile for CLI install, COPY, and ENTRYPOINT.
15+
16+
FROM 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-ci-image:tag
17+
18+
# Guest MicroVMs in this template are aarch64. The FROM image must be linux/arm64.
19+
# AWS also requires Linux, snapshot-compatible, and reachable from Lambda build
20+
# (public internet or ECR in the same account).
21+
#
22+
# Required on the guest (skip any your CI image already has). Package manager
23+
# depends on FROM (dnf / yum / apt-get / apk). Amazon Linux 2023 example:
24+
# RUN dnf install -y --setopt=install_weak_deps=0 \
25+
# bash tar gzip git ca-certificates findutils awscli python3 \
26+
# $(command -v curl >/dev/null 2>&1 || echo curl) && \
27+
# dnf clean all
28+
#
29+
# If the CI image sets USER to non-root, switch back so CLI install and
30+
# /opt/cursor land where hook.py and entrypoint.sh expect:
31+
# USER root
32+
33+
RUN missing=""; \
34+
command -v python3 >/dev/null || missing="${missing} python3"; \
35+
command -v git >/dev/null || missing="${missing} git"; \
36+
command -v curl >/dev/null || missing="${missing} curl"; \
37+
command -v tar >/dev/null || missing="${missing} tar"; \
38+
command -v aws >/dev/null || missing="${missing} awscli"; \
39+
if [ -n "${missing}" ]; then \
40+
echo "FROM image is missing:${missing}" >&2; \
41+
echo "Install them with this image's package manager, or uncomment the Amazon Linux dnf block above." >&2; \
42+
exit 1; \
43+
fi
44+
45+
# CLI install matches Dockerfile. Empty cursor-agent-version = prod installer.
46+
# create-microvm-image zips this directory and may not pass --build-arg.
47+
COPY cursor-agent-version /tmp/cursor-agent-version
48+
ARG CURSOR_AGENT_VERSION=
49+
# Guest MicroVMs here are aarch64. The previous x64 default installed a node
50+
# binary that failed at runtime with "cannot execute binary file".
51+
ARG CURSOR_AGENT_ARCH=arm64
52+
RUN VERSION="$(sed -e 's/#.*//' -e 's/[[:space:]]//g' /tmp/cursor-agent-version | sed -e '/^$/d' | head -n 1)" && \
53+
VERSION="${VERSION:-${CURSOR_AGENT_VERSION:-}}" && \
54+
ARCH="${CURSOR_AGENT_ARCH:-arm64}" && \
55+
echo "installing cursor-agent ${VERSION:-prod} linux/${ARCH} (build uname=$(uname -m))" && \
56+
if [ -n "${VERSION}" ]; then \
57+
mkdir -p "/root/.local/share/cursor-agent/versions/${VERSION}" /root/.local/bin && \
58+
curl -fsSL "https://downloads.cursor.com/lab/${VERSION}/linux/${ARCH}/agent-cli-package.tar.gz" \
59+
| tar --strip-components=1 -xzf - -C "/root/.local/share/cursor-agent/versions/${VERSION}" && \
60+
ln -sf "/root/.local/share/cursor-agent/versions/${VERSION}/cursor-agent" /root/.local/bin/agent && \
61+
ln -sf "/root/.local/share/cursor-agent/versions/${VERSION}/cursor-agent" /root/.local/bin/cursor-agent; \
62+
else \
63+
curl -fsSL https://cursor.com/install | bash; \
64+
fi && \
65+
ln -sf /root/.local/bin/agent /usr/local/bin/agent && \
66+
ln -sf /root/.local/bin/agent /usr/local/bin/cursor-agent && \
67+
rm -f /tmp/cursor-agent-version
68+
69+
ENV PATH="/root/.cursor/bin:/root/.local/bin:/usr/local/bin:${PATH}"
70+
ENV HOME=/root
71+
ENV NODE_COMPILE_CACHE=/tmp/cursor-compile-cache
72+
WORKDIR /opt/cursor
73+
COPY entrypoint.sh hook.py /opt/cursor/
74+
RUN chmod +x /opt/cursor/entrypoint.sh /opt/cursor/hook.py && mkdir -p /opt/cursor/workspaces
75+
ENV HOOK_PORT=9000
76+
ENTRYPOINT ["python3", "/opt/cursor/hook.py"]

0 commit comments

Comments
 (0)