skip to content

TeamCity

JetBrains' CI server: build configurations wired into chains that pass artifacts along, a pool of agents, and pipelines defined in a typed Kotlin DSL rather than YAML. Comes up in JVM-heavy shops, and the Kotlin DSL is a good prompt for discussing typed versus YAML pipeline configuration.

on this pageshow

explore

questions

4

In a TeamCity build chain, how does a snapshot dependency differ from an artifact dependency between two build configurations?

level: middleimportance: must knowfreq 62%

answer

  1. Two independent edge types, not one
  2. Snapshot means order plus same revision
  3. Artifacts move files, nothing else
  4. Missing snapshot: files from another build
  5. Reuse depends on revision consistency

basics

~20 s

A snapshot dependency orders the builds and pins them to one source revision, forming the chain. An artifact dependency copies files produced by the upstream build into the downstream one. They are independent settings, and most real chains declare both between the same pair.

solid answer

~50 s

TeamCity composes work from separate build configurations rather than stages in one file, and it wires them with two distinct dependency types. A **snapshot dependency** says "this build cannot start until that build finishes, and both must be built from the same snapshot of the sources" — that revision consistency is what makes a chain a chain, and it is what lets TeamCity reuse a suitable finished build instead of rerunning it. An **artifact dependency** says "before this build starts, download these files from that build", governed by artifact rules such as `+:target/*.jar => libs`. Declaring only an artifact dependency is legal and is the classic trap: with no snapshot dependency the source build is resolved by a rule such as last finished build, so a deploy can pick up binaries from a revision it was never triggered for.

code

kotlin · 20 lines
kotlin
object Deploy : BuildType({
    name = "Deploy"

    steps {
        script {
            scriptContent = "./deploy.sh libs/service.jar"
        }
    }

    dependencies {
        dependency(Build) {
            snapshot {
                onDependencyFailure = FailureAction.FAIL_TO_START
            }
            artifacts {
                artifactRules = "+:target/*.jar => libs"
            }
        }
    }
})

go deeper

for a junior

Be able to say TeamCity links separate build configurations into a chain, and that one dependency type controls order while another moves the files the next build needs.

for a middle

Explain both edges precisely: a snapshot dependency gives ordering plus a shared source revision, an artifact dependency copies published files by artifact rules, and real chains usually declare both.

for a senior

Diagnose the failure: an artifact dependency without a snapshot dependency resolves to some other build's output, so a deploy ships a binary from a revision nobody triggered. Explain how revision consistency makes build reuse safe.

for a principal

Own the chain topology — how far to fan out, where composite builds give a meaningful status boundary, and how reuse of already-built nodes keeps a large graph affordable without weakening the build-once guarantee.

## A different composition model Most CI tools let you write a pipeline as an ordered list of stages inside one file. TeamCity's unit is the **build configuration**: a first-class object with its own history, parameters, triggers, artifacts and agent requirements. A pipeline is therefore not a list — it is a graph of build configurations linked by dependencies, and TeamCity calls that graph a **build chain**. Triggering the tip of the chain triggers everything it depends on. Two kinds of edges build that graph, and confusing them is the single most common TeamCity misunderstanding. ## Snapshot dependency: ordering plus one revision A snapshot dependency from `Deploy` to `Build` means two things at once: 1. **Ordering.** `Deploy` will not start until `Build` finishes. 2. **Revision consistency.** Every build in the chain runs against the *same* snapshot of the VCS sources. If three commits land while the chain is running, the downstream builds still use the revision the chain started from, not the newest head. That second property is the reason snapshot dependencies exist. Without it, a chain of five configurations could each check out a slightly different commit, and the artifacts you deploy would not correspond to the sources you tested. Revision consistency also enables **reuse**: if a suitable finished build already exists for that exact snapshot, with the same parameters, TeamCity can attach it to the new chain instead of rebuilding. That is what makes chains cheap in a monorepo where most of the graph is unchanged. Snapshot dependencies also carry failure behaviour. You choose what happens when the upstream build fails or is cancelled — typically the dependent build does not start at all, and the whole chain is marked failed: ```kotlin dependencies { dependency(Build) { snapshot { onDependencyFailure = FailureAction.FAIL_TO_START onDependencyCancel = FailureAction.CANCEL } artifacts { artifactRules = "+:target/*.jar => libs" } } } ``` ## Artifact dependency: files, not ordering An artifact dependency is about **bytes**. Before the dependent build's steps run, TeamCity downloads files published by the source build into the dependent build's checkout directory, according to artifact rules. A rule has the form `+:<source pattern> => <target directory>`, with `-:` to exclude; archives can be unpacked by referencing a path inside them. Crucially, an artifact dependency does **not** by itself say which build to take the files from. It is resolved by a rule — the last finished build, the last successful or pinned build, a build with a given tag, or *the build from the same chain*. The last option is the one you want in a pipeline, and it only exists meaningfully when a snapshot dependency ties the two configurations into one chain. ## The failure this produces The realistic incident looks like this. A `Deploy` configuration has an artifact dependency on `Build`, taking the last successful build's JAR, but no snapshot dependency. Someone triggers `Deploy` to ship the fix they just merged. Meanwhile a later commit has already produced a newer successful `Build`. `Deploy` happily takes *that* JAR. Nothing errors; the deployed binary is simply not the change anyone thought they were shipping, and the chain shows green. The fix is to add the snapshot dependency so the pair forms a chain and the artifacts resolve to that chain's build. The symmetrical mistake is a snapshot dependency with no artifact dependency: the ordering is right, the revision is right, and the downstream build then rebuilds the module from source because nothing handed it the binary. It works, it is slow, and it quietly abandons the build-once discipline — the thing you deploy is not the thing you tested. ## Composite builds and chain shape Because each node is a real configuration, chains fan out naturally: one `Build` with several test configurations depending on it, and a single downstream node depending on all of them. TeamCity also offers a **composite** build configuration — one that runs no steps and occupies no agent, existing purely to aggregate the status of its dependencies into a single green or red result you can trigger, watch, or attach a status check to. ## What to say in an interview Name the two edges, state that snapshot is ordering *and* revision consistency while artifact is file transfer, note that they are orthogonal and usually declared together, and give the failure mode: an artifact dependency without a snapshot dependency resolves to some other build's output. If you can add that revision consistency is what makes build reuse safe, you have covered the model.

  • What does artifact rule syntax like +:target/*.jar => libs actually do?
    It selects files from the source build's published artifacts and places them in the dependent build's checkout. `+:` includes, `-:` excludes, the pattern is matched against artifact paths, and everything after `=>` is the target directory created in the dependent build. Paths inside published archives can be referenced too, so a zip can be unpacked selectively.
  • Why can TeamCity reuse an already-finished build inside a new chain?
    Because a snapshot dependency pins the chain to one source revision, TeamCity can tell that an existing finished build ran on exactly that revision with the same parameters. If so, it attaches that build to the new chain rather than rerunning identical work — the saving that makes large chains practical.
  • What is a composite build configuration used for?
    It runs no build steps and takes no agent. It exists to aggregate the results of the configurations it depends on into one status you can trigger and watch, so a chain of many parallel builds presents as a single green or red node instead of forcing people to inspect each one.

saying these in an interview costs you the question

  • Thinks an artifact dependency implies build ordering
  • Believes a snapshot dependency copies files downstream
  • Assumes every build in a chain checks out the newest commit
  • Wires deploy to "last successful build" and calls it a pipeline
  • Treats build configurations as stages inside one pipeline file

context

open as a page

In TeamCity, what is the Kotlin DSL, and how does a .teamcity/settings.kts file in your repository become the server's build configuration?

level: middleimportance: should knowfreq 55%

basics

~20 s

TeamCity's Kotlin DSL describes projects and build configurations as compiled Kotlin code in .teamcity/settings.kts. Enabling Versioned Settings on a project makes the server fetch that file from VCS, compile it, and apply the result as the project's live settings.

open as a page

A TeamCity build sits in the queue reporting that no compatible agents can run it. How do you diagnose and fix it?

level: seniorimportance: should knowfreq 44%

basics

~20 s

Open the build configuration's agents view, which lists compatible and incompatible agents with the exact unmet requirement for each. Fix the mismatch, or check the pool: agents in a pool not associated with the project can never run its builds however well they match.

open as a page

Your team is choosing between defining TeamCity pipelines in its Kotlin DSL and using a YAML-based CI configuration. What does the typed, compiled definition buy, and what does it cost?

level: principalimportance: nice to knowfreq 30%

basics

~20 s

A typed DSL catches configuration mistakes at compile time, offers IDE completion and safe refactoring, and lets one function generate many near-identical pipelines. It costs a compile step, coupling to the server's API version, and a configuration only JVM engineers can comfortably read and review.

open as a page