diff --git a/src/assets/images/artifacts/snapshot-cache-flow.svg b/src/assets/images/artifacts/snapshot-cache-flow.svg new file mode 100644 index 00000000000..d36325a2fc3 --- /dev/null +++ b/src/assets/images/artifacts/snapshot-cache-flow.svg @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + commit 1 + commit 2 + commit 3 + + + + + + + + + + + + + + + + + pnpm-lock.yaml + UNCHANGED + + + + + pnpm-lock.yaml + CHANGED + + + cache key + + hash(pnpm-lock.yaml) + + Is it cached? + + No + + + Step: install + Cache snapshot + + + + + + + + cache key + + hash(pnpm-lock.yaml) + + Is it cached? + + Yes + + + Step: + install + step + skipped + + + + Serve cached + snapshot + + + + cache key + + hash(pnpm-lock.yaml) + + Is it cached? + + No + + Step: install + Cache snapshot + + + diff --git a/src/components/ArtifactsCIPipelineDiagram.astro b/src/components/ArtifactsCIPipelineDiagram.astro new file mode 100644 index 00000000000..91ee46bb623 --- /dev/null +++ b/src/components/ArtifactsCIPipelineDiagram.astro @@ -0,0 +1,790 @@ +--- +interface Props { + autoPlay?: boolean; + loop?: boolean; +} + +const { autoPlay = true, loop = false } = Astro.props; +--- + +
+ +
+ + + + diff --git a/src/components/ArtifactsCIWorkflowDiagram.astro b/src/components/ArtifactsCIWorkflowDiagram.astro new file mode 100644 index 00000000000..42a652f1a84 --- /dev/null +++ b/src/components/ArtifactsCIWorkflowDiagram.astro @@ -0,0 +1,499 @@ +--- +import { Icon as AstroIcon } from "astro-icon/components"; + +interface Props { + autoPlay?: boolean; + loop?: boolean; +} + +const { autoPlay = true, loop = true } = Astro.props; +--- + +
+ +
+
+ Artifacts repo changes +
+ + Repository changes +
+
+ + + +
+ CI Workflow + +
+ 1 + + Install & cache dependencies +
+ + + +
+
+ 2 + Run CI steps +
+
+
+ + Build +
+
+ + Lint +
+
+ + Typecheck +
+
+ + Format +
+
+
+
+ + + +
+
+ + Deploy Worker +
+
+
+
+
+ + + + diff --git a/src/components/ArtifactsPlatformSharedCIDiagram.astro b/src/components/ArtifactsPlatformSharedCIDiagram.astro new file mode 100644 index 00000000000..d15c6a8f85d --- /dev/null +++ b/src/components/ArtifactsPlatformSharedCIDiagram.astro @@ -0,0 +1,227 @@ +--- + +--- + +
+ +
+
+ Platform namespace +
+
Artifact repo A
+
Artifact repo B
+
Artifact repo C
+
Artifact repo D
+
Artifact repo E
+
+
+ + + +
+ Shared CI workflow + authored & owned by platform +
+ build + lint + test + typecheck +
+
+ + + +
+
deploy A
+
deploy B
+
deploy C
+
deploy D
+
deploy E
+
+
+
deploy per repo
+
+
+ + diff --git a/src/content/changelog/artifacts/2026-08-04-build-and-deploy-on-push.mdx b/src/content/changelog/artifacts/2026-08-04-build-and-deploy-on-push.mdx new file mode 100644 index 00000000000..c566a8925e2 --- /dev/null +++ b/src/content/changelog/artifacts/2026-08-04-build-and-deploy-on-push.mdx @@ -0,0 +1,75 @@ +--- +title: Build and deploy Artifacts repos on every push +description: Trigger a Workflow on push to run CI checks and deploy your Artifacts repo to a Worker or User Worker. +products: + - artifacts + - workflows +date: 2026-08-04 +publish_future_dated_entry: true +--- + +import { TypeScriptExample, WranglerConfig } from "~/components"; + +You can now run your CI/CD pipeline on your [Artifacts](/artifacts/) repo by defining a CI [Workflow](/workflows/) with the [CI SDK](https://github.com/cloudflare/ci), automatically triggered on Artifacts push events. + +This allows you to: + +- Automatically build and deploy application code stored in Artifacts. +- Run linting, type checking, tests, and other checks on every push. +- Reuse dependencies when the lockfile (i.e. `pnpm-lock.yaml`) has not changed. +- Stop deployment when a check or build fails. +- Restrict API token access to the deployment step. +- Deploy the output to a [Worker](/workers/) or a [Workers for Platforms](/cloudflare-for-platforms/workers-for-platforms/) User Worker. + +Define your CI steps with `@cloudflare/ci`. Each `ci.runner()` spins up an isolated sandbox, and the `cache` option reuses installed dependencies across each sandboxed step in your CI job. + +Point `cache.inputs` at your lockfile (i.e. `pnpm-lock.yaml`, `bun.lock`), and the install step only runs again when that lockfile changes: + + + +```ts +const deps = await ci.runner({ + name: "install", + command: "bun install --frozen-lockfile", + cache: { inputs: ["package.json", "bun.lock"] }, +}); + +await Promise.all([ + deps.runner({ name: "lint", command: "bun run lint" }), + deps.runner({ name: "test", command: "bun run test" }), + deps.runner({ name: "typecheck", command: "bun run typecheck" }), + deps.runner({ name: "build", command: "bun run build" }), +]); + +await deps.runner({ name: "deploy", command: "bun wrangler deploy" }); +``` + + + +To start the Workflow automatically after each push, add a `cf.artifacts.repo.pushed` trigger to your Wrangler configuration: + + + +```jsonc +{ + "triggers": { + "events": [ + { + "type": "cf.artifacts.repo.pushed", + "filter": { + "namespace": "CI", + "repoName": "my-repo", + }, + "target": { + "scriptName": "my-ci-worker", + "workflowName": "ci-workflow", + }, + }, + ], + }, +} +``` + + + +To learn more, refer to [Build and deploy Artifacts repos](/artifacts/guides/build-and-deploy-on-push/). diff --git a/src/content/docs/artifacts/guides/build-and-deploy-on-push.mdx b/src/content/docs/artifacts/guides/build-and-deploy-on-push.mdx new file mode 100644 index 00000000000..abbfceee6a7 --- /dev/null +++ b/src/content/docs/artifacts/guides/build-and-deploy-on-push.mdx @@ -0,0 +1,250 @@ +--- +title: Build and deploy Artifacts repos +description: Build projects stored in Artifacts repos and deploy them as Workers or Workers for Platforms User Workers. +pcx_content_type: how-to +sidebar: + order: 5 +products: + - artifacts +--- + +import { + DashButton, + PackageManagers, + Tabs, + TabItem, + TypeScriptExample, + WranglerConfig, +} from "~/components"; +import ArtifactsCIPipelineDiagram from "~/components/ArtifactsCIPipelineDiagram.astro"; +import ArtifactsCIWorkflowDiagram from "~/components/ArtifactsCIWorkflowDiagram.astro"; +import ArtifactsPlatformSharedCIDiagram from "~/components/ArtifactsPlatformSharedCIDiagram.astro"; + +Artifacts events can build and deploy projects stored in Artifacts repos. When a user or agent pushes a commit, the event triggers a [Workflow instance](/workflows/build/trigger-workflows/). + +Within the Workflow, you define a continuous integration (CI) pipeline with the `@cloudflare/ci` SDK to cache dependencies, run checks, and build the project. The final step in your CI pipeline can deploy the output to a [Worker](/workers/) or a [Workers for Platforms](/cloudflare-for-platforms/workers-for-platforms/) User Worker. + +This is useful when you need to: + +- Automatically build and deploy application code stored in Artifacts. +- Run linting, type checking, tests, and other checks on every push. +- Reuse dependencies when the lockfile (i.e. `pnpm-lock.yaml`) has not changed. +- Stop deployment when a check or build fails. +- Restrict API token access to the deployment step. +- Deploy the output to a [Worker](/workers/) or a [Workers for Platforms](/cloudflare-for-platforms/workers-for-platforms/) User Worker. + +## How it works + + + +1. **Push repo changes** — A `git push` to the Artifacts repo emits an `artifacts.repo.pushed` event that identifies the pushed repo, branch, and commit. +2. **Run the CI Workflow** — The event starts a Workflow that checks out the commit, installs and caches dependencies, and runs CI steps — build, lint, typecheck, and format — in parallel. A failed step stops the Workflow before deployment. +3. **Deploy the Worker** — The Workflow deploys the built Worker either directly to your account or as a User Worker, if using Workers for Platforms + +### Run the CI Workflow + +Use the `@cloudflare/ci` SDK to define the CI steps. The SDK provides two tools to help you build the pipeline: + +- **Runners** — each `runner()` call spins up an isolated sandbox and executes a shell command. You use the same commands you already run locally or in another CI system. Each runner captures its own logs, status, and output files. +- **Cache** — the `cache` option on a runner caches installed dependencies so that later runs do not reinstall them. Pass the files that determine the dependencies, such as `pnpm-lock.yaml`, to `cache.inputs`. When those files have not changed, the SDK restores the cached result instead of running the command again. + +![Diagram showing three sequential commits: commit 1 has a cache miss so the install step runs and its sandbox snapshot is cached; commit 2 has an unchanged pnpm-lock.yaml so the cache key matches and the cached snapshot is served, skipping install; commit 3 has a changed pnpm-lock.yaml so the cache key misses and install runs again.](~/assets/images/artifacts/snapshot-cache-flow.svg) + +A cached runner takes a [snapshot](/sandbox/api/backups/) of its sandbox, which later runners reuse. Multiple runners can branch from the same cached result — for example, lint, type-check, and test runners can all reuse one cached install. + + + +A failed runner retries according to its [step configuration](/workflows/build/sleeping-and-retrying/#retry-steps), where you can define the number of retry attempts, backoff schedule, and timeouts. Runners that depend on a previous step do not start until its retry succeeds. If the configured retry limit is reached, the Workflow terminates in an `Errored` state. + +Here is an example of how to set up your Workflow to use runners and cache to install dependencies, run checks, build the project, and deploy the Worker: + + + +```ts +import { CIWorkflow } from "./src/pipeline"; +import type { + CiContext, + CiParams, + CiRunnerResult, + CloudflareArtifacts, +} from "./src/pipeline"; +import type { WorkflowEvent, WorkflowStep } from "cloudflare:workers"; + +export class CI extends CIWorkflow { + protected async pipeline( + _event: WorkflowEvent>, + _step: WorkflowStep, + ci: CiContext, + ): Promise { + // Install once, then run independent checks from the shared snapshot. + const deps: CiRunnerResult = await ci.runner({ + name: "install", + command: "bun install --frozen-lockfile", + cache: { inputs: ["package.json", "bun.lock"] }, + }); + + await Promise.all([ + deps.runner({ name: "lint", command: "bun run lint" }), + deps.runner({ name: "test", command: "bun run test" }), + deps.runner({ name: "typecheck", command: "bun run typecheck" }), + deps.runner({ name: "build", command: "bun run build" }), + ]); + + await deps.runner({ + name: "deploy", + command: "bun wrangler deploy", + cloudflareCredentials: { + accountId: this.env.CLOUDFLARE_DEPLOY_ACCOUNT_ID, + }, + }); + } +} +``` + + + +:::note +The credentials on the `deploy()` step are only required when deploying the Worker to an account other than the account where the CI Workflow is running. +::: + +## Start a build when code changes + +When a user or agent pushes a commit to an Artifacts repo, Artifacts emits an event that identifies the repo, branch, and commit that changed. You will use this event to trigger a Workflow instance which runs a CI pipeline by automatically checking out the commit and cloning the repo before installing dependencies, running checks, building the project, and/or deploying the Worker, according to your code. + +This guide defines those CI steps in a Workflow class named `CIWorkflow`. To start this Workflow automatically after each push, add an `cf.artifacts.repo.pushed` trigger to your Wrangler configuration. You should also include: + +- R2 binding: the bucket where your the snapshot of your cached dependencies will be stored +- Container (and Durable Object) binding: create a binding to your container to access sandboxes during each `runner()` step +- Workflows binding +- Artifacts binding +- Observability (optional): inspect your CI jobs as a Workflow instance with [Workers observability](/workers/observability/) + + + +```jsonc +{ + "$schema": "node_modules/wrangler/config-schema.json", + "name": "", + "main": "src/index.ts", + "compatibility_date": "2026-06-16", + "compatibility_flags": ["nodejs_compat"], + "artifacts": [ + { + "binding": "ARTIFACTS", + "namespace": "" + } + ], + "containers": [ + { + "class_name": "CiSandbox", + "image": "./Dockerfile", + "max_instances": 10, + "instance_type": "standard-4" + } + ], + "durable_objects": { + "bindings": [ + { + "name": "SANDBOX", + "class_name": "CiSandbox" + } + ] + }, + "workflows": [ + { + "name": "", + "binding": "CI_WORKFLOW", + "class_name": "CI" + } + ], + "exports": { + "CiSandbox": { + "type": "durable-object", + "storage": "sqlite" + } + }, + "r2_buckets": [ + { + "binding": "BACKUP_BUCKET", + "bucket_name": "" + } + ], + "triggers": { + "events": [ + { + "type": "cf.artifacts.repo.pushed", + // filter is optional. If you don't set repoName we will run the same workflow for every push on any repo in your Artifacts namespace + "filter": { + "namespace": "CI", + "repoName": "my-repo" + }, + "target": { + "scriptName": "", + "workflowName": "" + } + } + ] + }, + "observability": { + "enabled": true, + "logs": { + "enabled": true + } + } +} +``` + + + +:::note +If you are running CI for a User Worker, include `"dispatch_namespace": ""` in your trigger target. +::: + +### View build status + +The `[observability]` setting in your Wrangler configuration records the status and logs for each pipeline run. To identify which stage failed, inspect the instance in the Workflows dashboard: + + + +Each runner displays its own input, output, and status, so you can identify the command that failed. When a runner fails, the Workflow records its output and does not start stages that need its files. + +## Deploy the application + +To deploy a Worker, pass `wrangler deploy` to your final `runner()` step, i.e. `workspace.runner({ name: "deploy", command: "wrangler deploy" })`. + +To deploy a User Worker, pass `wrangler deploy --dispatch_namespace ` to your final `runner()` step. + +## Run one workflow for every repo in a namespace + +The `filter` in your trigger is optional. When you set `repoName`, only pushes to that specific repo start the Workflow. When you omit `repoName`, Cloudflare runs the same Workflow for every push to any repo in your Artifacts namespace. + +This is useful for platforms that author and own a single CI Workflow and want to apply it uniformly across every customer repo in a namespace. Instead of maintaining a separate trigger per repo, one shared Workflow builds, checks, and deploys each repo on push. + + + +To run the same Workflow for every repo in a namespace, drop `repoName` from the trigger `filter` and keep only the `namespace`: + + + +```jsonc +{ + "triggers": { + "events": [ + { + "type": "cf.artifacts.repo.pushed", + "filter": { + "namespace": "CI" + }, + "target": { + "scriptName": "my-ci-worker", + "workflowName": "ci-workflow" + } + } + ] + } +} +``` + + + +Each push still starts its own Workflow instance for the repo, branch, and commit that changed, so you can deploy a separate Worker per repo from the same shared Workflow definition.