skip to content

An Azure Pipelines `deployment:` job runs a script stored in the repository and fails with a file-not-found error, yet the identical step works in a regular `job:`. What is the cause?

level: juniorimportance: nice to knowfreq 36%

answer

  1. one job type clones, the other does not
  2. the implicit first step differs
  3. it assumes you already built it
  4. one keyword fixes it
  5. build once, deploy many

basics

~10 s

Deployment jobs do not check out the source repository automatically the way regular jobs do, so the working directory has no repo files. Add an explicit - checkout: self step before the script.

solid answer

~40 s

In Azure Pipelines, a regular `job:` performs an implicit `checkout: self` as its first step, so the repository is on disk. A `deployment:` job does not — it assumes you are deploying an artifact that was already built, not compiling source again. So the script the step references simply is not there. The fix is one line: add `- checkout: self` as the first step inside the strategy hook. The mirror-image default is worth knowing at the same time: a deployment job *does* automatically download the pipeline artifacts published earlier in the run, which you can turn off with `- download: none`. If your deployment scripts live in the repository, either check out the repo or publish those scripts as part of the artifact so the deployment consumes exactly what was built.

code

yaml · 11 lines
yaml
- deployment: DeployWeb
  environment: production
  pool:
    vmImage: ubuntu-latest
  strategy:
    runOnce:
      deploy:
        steps:
        - checkout: self
        - download: none
        - script: ./scripts/deploy.sh

go deeper

for a junior

Remember the asymmetry: regular jobs check out the repo implicitly, deployment jobs do not. Say - checkout: self and you have answered the question.

for a middle

Explain the mirrored defaults — no implicit checkout, but an implicit download of the run's pipeline artifacts — and name - download: none as the switch for the second one.

for a senior

Argue the design intent: the defaults enforce build-once-deploy-many, and shipping deploy scripts inside the artifact keeps a rollback honest instead of pairing an old build with a new script.

for a principal

Set the team convention for where deployment scripts live and how they are versioned, so a rollback restores the whole deployable unit rather than just the binary.

## The two implicit steps Azure Pipelines injects steps you never wrote. Which ones depends on the job type, and the asymmetry is the whole answer here. A regular `job:` gets an implicit `checkout: self` — the repository is cloned into `$(Build.SourcesDirectory)` before your first step. You can suppress it with `- checkout: none` or tune it with `fetchDepth`, `clean`, `submodules` and similar options. A `deployment:` job gets **no** implicit checkout. Instead it gets an implicit download of the current run's published pipeline artifacts into `$(Pipeline.Workspace)`. Suppress that with `- download: none`, or point it elsewhere with an explicit `- download:` step. ## Why the defaults are opposite The design encodes build-once-deploy-many. A build job needs source; a deployment job is supposed to take the artifact the build already produced and put it somewhere. If the deployment re-cloned and rebuilt, the thing reaching production would not be the thing that was tested, and rolling back would mean rebuilding rather than redeploying a known-good artifact. ```yaml - deployment: DeployWeb environment: production pool: vmImage: ubuntu-latest strategy: runOnce: deploy: steps: - checkout: self # not implicit here - script: ./scripts/deploy.sh ``` ## Symptoms you will actually see The failure is unglamorous and easy to misdiagnose: `No such file or directory`, `The term './deploy.ps1' is not recognized`, or a task reporting that a path matched no files. Engineers often chase the path variable — was it `Build.SourcesDirectory` or `Pipeline.Workspace`? — when the real answer is that nothing was cloned at all. Printing a directory listing as the first step settles it in seconds. ## Two legitimate fixes **Check out the repository.** Add `- checkout: self`. Fast to do, and fine when the deployment scripts are small and change with the code. **Publish the scripts into the artifact.** Include the deployment scripts in what the build publishes, so the deployment job's automatic artifact download brings them along. This is the stronger option: the scripts that deploy a given build are versioned *with* that build, so a rollback to an older artifact also rolls back to the deployment script that matched it. If you check out `self` instead, a redeploy of an old artifact runs today's script against yesterday's build. ## The related knob `- download: none` matters when the artifacts are large and the job does not need them — for example a deployment job that only runs a database migration or flips a feature flag. Downloading hundreds of megabytes you will not open is pure wall-clock waste on every deploy.

  • What does a deployment job download automatically, and how do you stop it?
    It downloads the pipeline artifacts published earlier in the same run, into `$(Pipeline.Workspace)`. Add `- download: none` inside the strategy hook to skip it — worth doing when the artifact is large and the job only runs a migration or flips a flag, so you are not paying a long download on every deploy.
  • Is it better to check out the repo or to ship the deploy scripts inside the artifact?
    Ship them in the artifact when you can. Then the script that deploys a build is versioned with that build, so redeploying an older artifact to roll back also uses the deployment script that matched it. Checking out `self` runs today's script against yesterday's build, which is a subtle way to break a rollback.

saying these in an interview costs you the question

  • Assuming every Azure Pipelines job clones the repo
  • Blaming the path variable instead of the missing checkout
  • Believing checkout: self is needed in ordinary build jobs too
  • Thinking the artifact download also brings repository files

context