Skip to content
CAMPUX Cloud Bootcamp Phase Four · Class Thirty-Nine
Phase Four — Operate, Secure & AI
Reading 14 min · Drills 4 · Part V of IX
Class Thirty-Nine · Pipelines
Class Thirty-Nine · Part V

Artifacts & packages — the thing you built, handed forward

One word, two jobs: the build output a pipeline passes from stage to stage, and the versioned package a whole company shares — and getting either wrong is how the thing you tested stops being the thing you ship.

§1

One word, two entirely different things

The word artifact is overloaded, and the overload trips up more engineers than any tool in this track. It names two things that live in different worlds and get confused daily, so name them apart on the first page and keep them apart forever. A pipeline artifact is the output of a build — a zipped web app, a compiled binary, a set of scripts — that one stage of a pipeline produces and hands to a later stage; it exists to move a built thing across the pipeline without rebuilding it. A package artifact is a versioned, shareable dependency — a NuGet package, an npm module, a Python wheel — that many projects pull in by name and version; it exists to let a piece of code be reused across a company without copy-and-paste. One is a handoff inside a single run. The other is a shared library the whole organisation depends on.

They feel similar because both are "compiled things you store somewhere," and both live under the word artifact in the Azure DevOps portal. But confusing them costs you real hours: you try to share a pipeline artifact between two separate pipelines and discover it vanishes when the run is deleted, or you version a package by hand and ship three subtly different copies of the same library to three teams. This part takes them one at a time — pipeline artifacts and the build-once discipline first, then package feeds and the versioning that turns a shared library into a contract — because the fix for each is different, and knowing which problem you have is half the job.

§2

Pipeline artifacts, and the rule that saves your Fridays

A pipeline artifact is how a build hands its output forward. One stage compiles or packages the code and publishes the result; a later stage — or a later job — downloads it and deploys it. In Azure DevOps the two verbs are exactly that plain: publish stores the folder as a named artifact, and download pulls it back in a job that depends on the one that published it. The artifact lives with the run that produced it and is exempt from storage billing; it is a handoff, not an archive, and deleting the run deletes the artifact with it.1

The reason this matters is a principle with a name: build once, deploy many. You build the artifact a single time, then promote that same artifact through staging and into production — rather than rebuilding it separately for each environment. Rebuilding per environment feels harmless and is quietly poisonous: a dependency updates between builds, a compiler flag differs, the machine has changed, and the binary you tested in staging is no longer the binary you shipped to customers. When it then fails only in production, you are debugging a thing that was never tested, and the trail is cold. One build, published once, carried forward unchanged, means the artifact that passed your tests is byte-for-byte the artifact your customers run.

Ship the binary you tested, not a copy.

The YAML is small enough to read in one breath: publish in the build stage, name it, then download it in a stage that depends on the first.

# build-once, deploy-many — publish in one stage, download in the next
stages:
- stage: build
  jobs:
  - job: package
    steps:
    - publish: $(Build.ArtifactStagingDirectory)/webapp
      artifact: webapp                # store the built folder once

- stage: deploy_staging
  dependsOn: build
  jobs:
  - job: release
    steps:
    - download: current               # pull the SAME artifact back
      artifact: webapp
    # ...deploy $(Pipeline.Workspace)/webapp — do not rebuild it

    
    
§3

Feeds — where shared packages live, and where public ones are cached

The other artifact is the package: a versioned unit of code — a NuGet package, an npm module, a Maven or Python or Universal Package — that other projects declare as a dependency and restore by name. Package artifacts do not belong to one pipeline run; they are meant to outlive it and be pulled in by many. In Azure DevOps they live in Azure Artifacts feeds. A feed is a managed, access-controlled store that hosts your packages — a single feed can hold several package types at once — and you can scope it to a project (visible only inside that project) or to the whole organisation (visible from every project in it). That scope is the first decision: a library three teams share wants an organisation-scoped feed; a package only one project ever touches is fine project-scoped.

The feature that turns a feed from a private cupboard into an actual supply chain is the upstream source. A feed can be configured to proxy a public registry — nuget.org, npmjs, PyPI — so that when a build asks the feed for a public package it does not already hold, the feed fetches it from the public source, saves a copy, and serves it. Point your build at the one feed and it gets both your private packages and the public ones, from a single URL. Two payoffs follow. Your builds become reproducible: once the feed has cached a version, that exact version keeps resolving even if the public registry is down or the author unpublishes it — the outage that famously breaks a thousand CI pipelines does not break yours.2 And you get a single place to curate and audit every third-party dependency entering your builds, which is where supply-chain control begins.

Table 1 — Reading a feed's configuration
SettingWhat it decidesGet it wrong and…
Feed & scopeWhere the packages live and who can see them — project-scoped (one project) or organization-scoped (every project in the org)A shared library sits project-scoped and the other teams cannot restore it
Package typeThe ecosystems the feed serves — NuGet, npm, Maven, Python, Cargo, Universal — all can coexist in one feedYou stand up a separate store per language instead of one feed for all
Upstream sourcesWhether the feed proxies and caches public registries (nuget.org, npmjs, PyPI) behind the same URLA registry outage or an unpublished version stops your builds cold
RetentionWhich old package versions are aged out to reclaim storage, and which are keptEither the feed grows without limit, or a version something still depends on is deleted

Retention is the quiet one. A feed accumulates versions forever unless you tell it not to, so a retention policy prunes the old ones — but packages are immutable and something may still depend on an old version, so the policy protects any version that is still referenced. Set it thinking about what could still need an old build, not about what looks tidy.

§4

SemVer — a version number that means something

A package is worthless to the people who consume it unless its version number carries information, and semantic versioning is the agreement that makes it do so. A version is three numbers, MAJOR.MINOR.PATCH, and each position promises something specific about what changed. Bump the PATCH for a backward-compatible bug fix; bump the MINOR when you add functionality that does not break anyone; bump the MAJOR when you make a breaking change existing callers must adapt to. Read the other way, the number is a message to every consumer: a jump from 2.4.1 to 2.4.2 says "safe, take it"; a jump to 3.0.0 says "read the notes before you upgrade, this will break you." The whole point is that a consumer can decide whether to take an update without reading your code — the number tells them.

Semantic version
A version string of the form MAJOR.MINOR.PATCH in which each field signals the kind of change since the last release: MAJOR for a breaking change, MINOR for a backward-compatible feature, PATCH for a backward-compatible fix. The number is a contract between the package's author and everyone who depends on it.

This is precisely why an internal shared library must be a versioned package and never a copied file. When five projects each hold their own copy of a helper library, a fix to one copy reaches exactly one project; the other four keep the bug, and no one can say which copy any given project is running — the codebase has quietly forked five ways. Move that library into a feed with a semantic version and the relationship inverts: you publish 1.2.3 once, every project references it by version, a fix ships as 1.2.4 and each team takes it deliberately, and the version in a project's manifest is an exact, auditable statement of which code it runs. Copy-paste hides that; a version number publishes it. That difference — between "somewhere there are five slightly different copies" and "everyone is on 1.2.4" — is the difference between a dependency you can reason about and one you cannot.

Case File · Campux Retail

Basecamp's shared code lived on a file share — so a fix never reached everyone

off the file share, into a feed, with a version that finally means something

The acquired Basecamp team had a folder of internal npm packages — order helpers, a tax-rounding library, a shared logging module — that every one of their projects used. It lived on a network file share, and each project got its code the only way the share allowed: someone copied the folder in. There were no versions, only copies, and the copies had drifted. When an engineer fixed a rounding bug in the tax library on the warehouse project, the storefront kept the bug, because nothing connected the two copies; worse, no one could say with confidence which project had which version of anything, so every change carried the quiet fear of breaking a caller nobody remembered.

The reader moves the shared code into an Azure Artifacts feed, publishes each library under a semantic version, and adds an upstream source so the same feed also serves the public npm packages the projects depend on — one URL for everything, cached and reproducible. Now the tax fix ships as a version bump, every project references the library by name and version, and taking the fix is a deliberate, visible act rather than a hopeful copy. A version number, at last, is a contract instead of a rumour — and the recurring "why is the total wrong on the website but right in the warehouse" ticket closes for good.

§5

Two artifacts, one discipline: what you ship is what you tested

Step back and the two halves of this part are the same idea wearing different clothes. Build once and deploy the same pipeline artifact everywhere, and the thing in production is provably the thing you tested. Publish a shared library once under a semantic version and consume it by that version everywhere, and the code in each project is provably the code you think it is. Both are refusals of the same quiet lie — that a thing rebuilt, or a folder copied, is "the same" as the original. It is not, and the gap between "the same" and "a copy of" is where the hardest bugs live: the ones that reproduce nowhere but where they matter.

This is also, plainly, hireable. An engineer who rebuilds per environment or shares code by copy-paste is describing the outages they will one day cause; an engineer who can say "we build once and promote the artifact, and our shared code is versioned in a feed with upstream caching" is describing a supply chain a team can trust. You do not need many sentences to signal which one you are — you need the right ones, and this part is where you earn them.

On the job

The bug that only happened in production

You · Cloud Engineer · the release that behaved differently once shipped

A release passes every test in staging and then misbehaves in production, and the first hour goes to a horrible suspicion: the two are not running the same build. Someone wired the pipeline to compile fresh for each environment, and a dependency moved between the two compiles. You change one thing — build the artifact once, publish it, and have every environment download that same artifact — and the class of bug simply stops existing, because there is now only one binary to be wrong about. No heroics; you closed the gap between what you tested and what you shipped. Most of the reliability in this domain looks exactly like that.

Class 39e

Examination

Four drills, then two situations. Write your answer before you reveal the reasoning, or the exercise is worthless. Nothing is stored.

Drill 01Recall
"Build once, deploy many" is the rule for pipeline artifacts. What does it actually protect you against?
Marked

B — the point is provenance, not disk or speed. When you build the artifact once and promote that same artifact through staging and into production, the thing customers run is byte-for-byte the thing your tests passed. Rebuild separately for each environment and a dependency can update, a flag can differ, or the machine can change between builds — so the production binary was never actually tested, and when it fails only there you are chasing a bug with a cold trail. A and C are real but minor side effects, not the reason the rule exists; the rule is a truth guarantee. D is a different topic entirely — branching, from Class Nineteen. The sentence to keep: ship the binary you tested, not one that looks like it.

Drill 02Recall
You add an upstream source to an Azure Artifacts feed, pointing it at nuget.org. What does that buy you?
Marked

B — an upstream source is a caching proxy, pointed inward. Your build asks the feed for a package; if the feed does not hold it, the feed fetches it from the public registry, saves a copy, and serves it — so every dependency, private and public, comes from one URL, and once a version is cached your builds stay reproducible even when the public source is unavailable or a version is unpublished. A reverses the direction: upstream sources pull public packages in, they do not push yours out. C invents an auto-upgrade that would be a disaster — SemVer exists precisely so upgrades stay deliberate. D is retention, a different feed setting entirely. The upstream source is where supply-chain control starts: one curated, auditable door for third-party code.

Drill 03Select three
Which three of these are true of semantic versioning and Azure Artifacts feeds?
Marked

MAJOR is breaking and PATCH is a safe fix; one feed holds many package types; scope decides visibility. Those three are the load-bearing facts. The first is the entire promise of SemVer, which makes the second false: the number exists so a consumer can decide whether to take an update without reading your code — a jump to a new MAJOR is the author saying "this will break you." The last reject is the copy-paste trap this part is built to kill: a copied folder has no version, so a fix to one copy never reaches the others and no one can say which copy any project runs. A versioned package in a feed makes each project's dependency an exact, auditable statement; five copies on a file share make it a guess.

Drill 04Spot the error
An engineer writes the release notes for the shared tax library. One line contradicts what its version number claims. Which?
# tax-lib release plan
1.  Fixed a rounding error in VAT totals — no API change.
    Release as 2.4.1 → 2.4.2  (PATCH).
2.  Added an optional currency parameter; old callers
    unaffected. Release as 2.4.2 → 2.5.0  (MINOR).
3.  Renamed calculateTax() to computeTax() and dropped
    the old name — every caller must update.
    Release as 2.5.0 → 2.5.1  (PATCH).
4.  Publish once to the feed; each project takes the new
    version when it chooses.
Marked

Line three — a PATCH bump on a breaking change is a lie the number tells. Renaming a method and dropping the old name breaks every caller, which is the definition of a MAJOR change: it must go to 3.0.0. Shipping it as 2.5.1 tells every consumer "safe patch, take it freely" — so their automated updates pull it in and their builds break with no warning, which is exactly the surprise SemVer exists to prevent. Lines one and two are correct: a compatible fix is PATCH, a compatible feature is MINOR, so A and B misread the scheme. D inverts the build-once rule — a package is published once and consumed by version everywhere; rebuilding per project is how you get the drift the feed was meant to end. The number is a contract; line three breaches it.

Situation 01Write before you reveal
A colleague proposes speeding up releases: "Let's have each environment build its own copy of the app right before it deploys — staging builds for staging, production builds for production. No waiting on a shared artifact." It would run. What do you say?
It works on every green day. Ask what it costs on the one red day — and what "the same build" actually means once there are two builds.
Reasoning

The trap is that the proposal is faster and demonstrably works — right up until it doesn't. Two builds are two chances to differ: a dependency that resolved to a new version between them, a base image that updated, a flag set differently. On every ordinary day the two copies are identical and the scheme looks fine, which is exactly why it survives to production and then, one day, the staging build and the production build are not the same binary — and the thing your tests approved is not the thing customers got. You cannot debug that quickly, because the evidence is in a build that no longer exists.

Name the principle, don't just object: build once, deploy many. Build the artifact a single time, publish it, and have staging and production both download that same artifact. It is not slower in any way that matters — one build is usually less total work than several — and it converts "the same, probably" into "the same, provably." The gain is not speed; it is that a passing test in staging becomes a real guarantee about production, because there is exactly one binary to reason about.

Concede the goal, redirect the method. Their instinct — make releases faster and less serial — is good, and worth saying so. The fix for slowness is caching, parallel jobs, and a leaner build, not a second build; speeding up delivery must never come at the price of not knowing what you shipped. Offer it as one sentence they can repeat: we build the artifact once and promote it — so production runs the exact bytes staging tested, not a fresh copy that might differ.

Situation 02Write before you reveal
A teammate has spent a day chasing a bug that only appears in one of your five projects. It turns out all five use a shared helper library — copied into each project as a folder, months ago, and since drifted. "I'll just fix it here and copy the folder into the others," they say. What do you advise, and what do you propose instead?
The copy-and-fix ends today's pain and guarantees next month's. What made the drift possible in the first place — and what removes the possibility, not just this instance?
Reasoning

Concede the fix, then name the disease. Yes, fix the bug now — but "copy the folder into the other four" is the exact move that created this mess, done again. It relies on a human remembering all five locations, this time and every time; the day someone forgets one, the fork reopens, and you are back here in a month with a sixth copy nobody can account for. The problem is not that this copy is stale. The problem is that there are copies at all, with no version to tell one from another.

Propose the structural fix: one versioned package in a feed. Publish the helper into an Azure Artifacts feed as a semantically versioned package — 1.0.0 — and have each project depend on it by name and version instead of holding a copy. The bug fix ships as 1.0.1; every project takes it deliberately by bumping one line, and each project's manifest now states exactly which version it runs. "Somewhere there are five slightly different copies" becomes "everyone is on 1.0.1, and I can prove it." That is the difference between a dependency you can reason about and one you can only hope about.

Close on the cost comparison, because that is what wins the argument. Copying folders is free today and expensive on a schedule — this exact lost day, on repeat. Standing up the feed and publishing once is an afternoon that ends the whole class of bug. Frame it as the trade it is: we can keep paying a day every few months to rediscover the drift, or spend one afternoon so a version number does the remembering for us. The version was never bureaucracy; it was the thing that lets a fix reach everyone.

Examination record · first attempt
0/4
Class 39e · Complete
Retain this much

Five things worth carrying out of this part

  1. "Artifact" means two things. A pipeline artifact is build output handed from one stage to the next inside a run; a package artifact is a versioned, shareable dependency in a feed. Different problems, different fixes — know which you have.
  2. Build once, deploy many. Build the artifact a single time, publish it, and promote that same artifact through every environment — so the bytes in production are the bytes you tested, not a rebuild that might differ.
  3. An Azure Artifacts feed hosts your packages and can be scoped to a project or the whole organization. An upstream source makes the feed proxy and cache public registries, so builds stay reproducible and third-party code has one auditable door.
  4. SemVer is MAJOR.MINOR.PATCH: breaking, feature, fix. The number lets a consumer decide whether to take an update without reading your code. A breaking change that ships as a PATCH is a lie the number tells.
  5. An internal shared library must be a versioned package, never a copied folder. Copies drift and a fix reaches only one; a version in a feed means every project's dependency is an exact, auditable fact — and a fix reaches everyone who takes it.
Notes
  1. The publish and download keywords are shortcuts for the Publish Pipeline Artifact and Download Pipeline Artifact tasks; you can write the full task: form instead, and older pipelines use "build artifacts" rather than the newer "pipeline artifacts." Pipeline artifacts are exempt from storage billing, but they belong to their run — delete the run and the artifact goes with it, which is exactly why a package you need to keep and share belongs in a feed, not in a run's artifacts.
  2. "Reproducible" is a direction, not a guarantee — do not oversell it. An upstream source caches a version once your feed has requested it, so a package pulled before an outage keeps resolving; a brand-new dependency you have never fetched still depends on the public registry being up at that moment. Caching removes the most common failure, not every failure. Treat the reproducibility as strong and worth having, and still pin your versions rather than trusting the network.