Skip to content

BM-2466: Build Docker images once per production deployment#1654

Open
capossele wants to merge 3 commits intomainfrom
angelo/share-docker-build-prod
Open

BM-2466: Build Docker images once per production deployment#1654
capossele wants to merge 3 commits intomainfrom
angelo/share-docker-build-prod

Conversation

@capossele
Copy link
Contributor

Production pipelines currently rebuild the same Docker image independently for each chain stack (Base Sepolia, Eth Sepolia, Base Mainnet, etc.), wasting ~20-40 minutes of redundant Rust/Docker builds per service per deploy.

This PR designates l-prod-84532 (Base Sepolia) as the "builder" production stack. It builds the Docker image once and exports the image reference as a Pulumi stack output. Dependent production stacks (l-prod-8453, l-prod-11155111, l-prod-1) read this reference via pulumi stack output, set it as a config value, and skip their Docker build entirely.

Changes:

  1. Pipeline orchestration: builder at runOrder: 2, dependent stacks at runOrder: 3 (blocked if builder fails)
  2. Buildspec: dependent stacks read image refs from the builder stack and inject them via pulumi config set
  3. All services (slasher, distributor, order-stream, order-generator, indexer): conditional logic to either build the Docker image or use a pre-built IMAGE_URI
  4. Indexer handles three separate images (MARKET_IMAGE_URI, REWARDS_IMAGE_URI, BACKFILL_IMAGE_URI)
  5. Fixed Pulumi anti-pattern: IAM RolePolicy resources were being created inside .apply() callbacks (order-stream, indexer-infra)

Staging is unchanged, each staging stack still builds its own image. Manual pulumi up without the pipeline also works normally (falls back to building).

@github-actions github-actions bot changed the title Build Docker images once per production deployment BM-2466: Build Docker images once per production deployment Feb 17, 2026
@linear
Copy link

linear bot commented Feb 17, 2026

Copy link
Contributor

@willpote willpote left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a good usecase for using stack references:

https://www.pulumi.com/tutorials/building-with-pulumi/stack-references/

Would simplify a lot of the pipeline shenanigans as we don't need to touch the build spec. Then we just try and get image output in the Pulumi typescript and use it if it exists

Comment on lines +72 to +90
const outputs: Record<string, pulumi.Output<string>> = {};

// Primary image (every service has one)
const dockerfile = config.require('DOCKERFILE');
const primaryImage = buildImage('primary', dockerfile, dockerTag);
outputs.imageRef = primaryImage.ref;

// Optional additional images (indexer has backfill + rewards)
const backfillDockerfile = config.get('DOCKERFILE_BACKFILL');
if (backfillDockerfile) {
const backfillImage = buildImage('backfill', backfillDockerfile, `backfill-${dockerTag}`);
outputs.backfillImageRef = backfillImage.ref;
}

const rewardsDockerfile = config.get('DOCKERFILE_REWARDS');
if (rewardsDockerfile) {
const rewardsImage = buildImage('rewards', rewardsDockerfile, `rewards-${dockerTag}`);
outputs.rewardsImageRef = rewardsImage.ref;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its kind of awkward how we have this special case. Its possible to do lists in pulumi config (https://www.pulumi.com/docs/iac/concepts/config/#structured-configuration), so I'm imagining something like:

images:
    paths:
    - dockerfiles/indexer.dockerfile
    - dockerfiles/rewards-indexer.dockerfile
    - dockerfiles/market-indexer.dockerfile

Then we can parse the name of the image from the path, and set outputs[name] = image.ref

(or we could just use a comma separated list)

Comment on lines +285 to +295
if [ -n "$BUILDER_STACK" ]; then
echo "Dependent prod stack: reading image refs from builder stack $BUILDER_STACK"
IMAGE_REF=$(pulumi stack output imageRef --stack "$BUILDER_STACK" 2>/dev/null) || IMAGE_REF=""
[ -n "$IMAGE_REF" ] && pulumi config set IMAGE_URI "$IMAGE_REF" && echo "Set IMAGE_URI=$IMAGE_REF" || true
MARKET_REF=$(pulumi stack output marketImageRef --stack "$BUILDER_STACK" 2>/dev/null) || MARKET_REF=""
[ -n "$MARKET_REF" ] && pulumi config set MARKET_IMAGE_URI "$MARKET_REF" && echo "Set MARKET_IMAGE_URI" || true
REWARDS_REF=$(pulumi stack output rewardsImageRef --stack "$BUILDER_STACK" 2>/dev/null) || REWARDS_REF=""
[ -n "$REWARDS_REF" ] && pulumi config set REWARDS_IMAGE_URI "$REWARDS_REF" && echo "Set REWARDS_IMAGE_URI" || true
BACKFILL_REF=$(pulumi stack output backfillImageRef --stack "$BUILDER_STACK" 2>/dev/null) || BACKFILL_REF=""
[ -n "$BACKFILL_REF" ] && pulumi config set BACKFILL_IMAGE_URI "$BACKFILL_REF" && echo "Set BACKFILL_IMAGE_URI" || true
fi
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we use stack refs I don't think we need any of this

Comment on lines +345 to +353
// Dependent prod stacks receive BUILDER_STACK so the buildspec can read
// the pre-built image ref instead of rebuilding the Docker image.
if (builderStack) {
envVars.push({
name: "BUILDER_STACK",
type: "PLAINTEXT",
value: builderStack,
});
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also not needed with stack refs, reading stack is all done at runtime in pulumi

@willpote
Copy link
Contributor

willpote commented Feb 19, 2026

One more thing, maybe lets test this on Slasher pipeline before updating doing all of them? This sort of Pulumi stuff is hard to get right the first time, and will avoid us breaking every pipeline :D

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments