> ## Documentation Index
> Fetch the complete documentation index at: https://site.aspect.build/llms.txt
> Use this file to discover all available pages before exploring further.

# Bazel technique for Continuous Delivery

> Learn about using Bazel for Continuous Delivery, distinguishing between CI, CD, and Deployment, and optimizing artifact delivery processes

export const BlogPost = ({title, date, authors, tags, image, children}) => {
  const tagList = tags ? tags.split(", ").filter(Boolean) : [];
  const tagSlug = t => t.toLowerCase().replace(/&/g, "").replace(/\+/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
  const formattedDate = date ? new Date(date + "T00:00:00").toLocaleDateString("en-US", {
    year: "numeric",
    month: "long",
    day: "numeric"
  }) : "";
  return <section className="w-full flex justify-center px-4 py-12 md:py-16">
      <div style={{
    maxWidth: "800px",
    width: "100%"
  }}>
        {image && (typeof image === "string" ? <img noZoom src={image} alt={title} className="w-full rounded-xl mb-8" style={{
    maxHeight: "400px",
    objectFit: "cover"
  }} /> : <div className="blog-post-hero-image">{image}</div>)}
        <h1 className="text-3xl md:text-4xl font-bold text-zinc-900 dark:text-white">
          {title}
        </h1>
        <div className="flex flex-wrap items-center gap-3 mt-4 text-sm text-zinc-500 dark:text-zinc-400">
          {authors && <span>{authors}</span>}
          {authors && formattedDate && <span>·</span>}
          {formattedDate && <span>{formattedDate}</span>}
        </div>
        {tagList.length > 0 && <div className="flex flex-wrap gap-2 mt-3">
            {tagList.map(tag => <a key={tag} href={"/blog/tags/" + tagSlug(tag)} className="px-2 py-0.5 rounded-full text-xs bg-zinc-100 dark:bg-zinc-800 text-zinc-600 dark:text-zinc-400 hover:bg-blue-100 dark:hover:bg-blue-900/40 hover:text-blue-700 dark:hover:text-blue-300 transition">
                {tag}
              </a>)}
          </div>}
        <hr className="my-8 border-zinc-200 dark:border-zinc-700" />
        <div className="prose dark:prose-invert max-w-none">{children}</div>
      </div>
    </section>;
};

export const MarketingPage = () => <div className="marketing-page-marker" style={{
  display: "none"
}} />;

export const Section = ({children, className = "", gray = false, dark = false, id}) => <section id={id} className={`w-full flex justify-center px-4 py-16 md:py-24 ${gray ? "bg-gray-50 dark:bg-zinc-900" : dark ? "bg-zinc-900 dark:bg-zinc-950" : ""} ${className}`}>
    <div className="w-full" style={{
  maxWidth: "1140px"
}}>
      {children}
    </div>
  </section>;

<MarketingPage />

<BlogPost title="Bazel technique for Continuous Delivery" date="2025-07-03" authors="Alex Eagle" tags="Selective Delivery, CI/CD, Bazel">
  <img noZoom src="https://mintcdn.com/aspectbuild/x1L7Iep716jCyJVo/images/blog/hashnode/hn-44f882ad1c.jpeg?fit=max&auto=format&n=x1L7Iep716jCyJVo&q=85&s=e3ce520f18be74952a3158397543eed7" alt="" className="blog-post-cover" width="1600" height="1065" data-path="images/blog/hashnode/hn-44f882ad1c.jpeg" />

  The term "CD" is ambiguous. Some engineers use it to mean "Continuous Deployment", in which changes are automatically released, e.g. into a "dev" environment.

  Aspect recommends that Continuous Delivery is modeled as the step of the pipeline where built artifacts are uploaded from the build machine to a well-known repository location. This could be a container image registry like Docker Hub, a blob store like AWS S3, or even a database.

  This makes a clear separation of responsibilities between CI, CD and Deployment:

  * The CI pipeline runs all the tests to confirm the repo is in a shippable state

  * The CD pipeline should then upload only the artifacts that are:

    * configured with `BUILD.bazel` files. Product engineers don't need to worry about setting up CD

    * green: it can prove that all relevant tests are passing

    * changed from a previous build

  * The deployment system

    * locates and "promotes" artifacts to the next environment, such as "dev", "staging", or "prod".

  ## Build vs. Buy

  The recommendations in this guide can be applied in two ways:

  * DevInfra teams may wish to implement and operate a custom system for their organization, or

  * Use [Aspect Workflows](https://aspect.build/workflows), which provides this feature out-of-the-box.

  ## What is "deliverable"

  A deliverable artifact is one that contains both the binary or files to push, as well as the "pushing" logic that knows how to perform the upload. It might also send a message to the deployment system to trigger an auto-deployment of the new artifact.

  In Bazel terms, this means a deliverable should be an executable program that can be `bazel run`.

  ### Container Images

  The rules\_oci [`oci_push`](https://github.com/bazel-contrib/rules_oci/blob/main/docs/push.md#oci_push) or rules\_docker [`container_push`](https://github.com/bazelbuild/rules_docker/blob/master/docs/container.md#container_push) rules can both be executed with `bazel run` to push a Docker image to a registry like Docker Hub.

  Therefore these rules are considered "deliverable".

  ### Git Push

  Sometimes artifacts belong in a separate code repository. For example, an SDK built from the API definitions in a monorepo needs to be published.

  See an example `git_push` executable [in this repository](https://github.com/aspect-build/bazel-examples/tree/main/git_push).

  ### S3 upload

  See [s3\_sync](https://github.com/aspect-build/rules_aws/tree/main/examples/release_to_s3).

  ## Which targets to deliver

  A `bazel query` expression is the most convenient way to locate deliverable targets. Users may choose a tagging scheme for their workspace (i.e. "all targets with `tags = ['artifact']`"), or deliver well-known rule kinds (i.e. `oci_push`), or both.

  ## Which changes to deliver

  To optimize time and money, it's best to deliver only "changed" targets. This avoids wasted time and resources uploading the same artifact repeatedly. It also means that release engineers won't have to sort through a massive list of duplicates when choosing a release.

  There are two approaches for choosing "changed" targets:

  1. Predict the changes based on a version control delta. For example you could `git diff` between the hash being delivered and the "prior successful" delivery hash, then use a tool like [bazel-diff](https://github.com/Tinder/bazel-diff) or [target-determinator](https://github.com/bazel-contrib/target-determinator) to produce a list of targets that might be affected by those changes.

  2. Determine empirically based on what is actually different. This requires determinism, so it must only use [unstamped](https://github.com/bazel-contrib/bazel-lib/blob/main/docs/stamping.md) build results (`--nostamp`). In most cases a green CI run just completed, so these unstamped outputs are easily available.

  Aspect recommends following the second approach because the first has some downsides:

  * [bazel-diff](https://github.com/Tinder/bazel-diff) is incorrect and will sometimes miss affected targets, so they aren't delivered.

  * [target-determinator](https://github.com/bazel-contrib/target-determinator) is slow and may hurt the "service level indicator" of time between pushing a hotfix and being able to release that fix.

  * It will over-deliver, because sometimes a source change doesn't actually factor into whether the release binary changes, such as for a comment-only change.

  The rest of this section provides more details about the second approach (determine empirically).

  To determine whether Workflows should deliver that executable target on a particular commit, it is first hashed using the [`aspect outputs` command](https://docs.aspect.build/cli/commands/aspect_outputs/) with a special pseudo-mnemonic "ExecutableHash", for example:

  ```sh theme={null}
  $ aspect outputs 'attr("tags", "\bdeliverable\b", //...)' ExecutableHash
  //cli:release h1:cj8OUC3l3fIr3Zxnffk6y7gukLOJmiWRCAQoqadg66Y=
  //workflows/rosetta:release h1:kjHVajw+Nta2kh3Epcd32DkZxTE1NHA8b5N7hCNFNSM=
  ```

  You need a lookup database to store previously delivered hashes. If the hash value matches one previously seen, then skip delivery of that target.

  ### Debugging changes to deliver

  You can run the `aspect outputs` command locally to understand whether a given change to a source file results in a new executable. Sometimes the result may be surprising. For example, if a comment in a `.go` source file is changed, the compiler produces the same `.a` file as a result, so the hash seen on the uploader executable is unchanged.

  Another scenario that won't change the executable is when some production configuration is changed. For example, you may use Helm charts to deploy to Kubernetes. If these aren't included inside the image, then changes to these files won't cause a new delivery.

  ## Perform the delivery

  Run each deliverable target with [stamping](https://registry.bazel.build/modules/bazel_lib#lib-stamping-bzl) enabled. You can do this in a script which reads the targets from a manifest file, essentially `cat $delivery_manifest | xargs -N1 bazel run --stamp`.
</BlogPost>
