teamcity-and-octopus
Teamcity basics
Teamcity is a flexible CI tool that can build artifacts from source code and run pipelines to test them.
Octopus deploy is an extremely flexible continuous deployment tool which deploys code artifacts to a wide variety of environments

How TeamCity works
TeamCity contains two main components:
- TeamCity server: the server that you can self-host on-prem that contains all the CI and config info that you use for your team.
- It is responsible for project configurations, managing user permissions, scheduling builds, and maintaining build data.
- TeamCity build agent: the agent that actually does the building and execution of pipelines.
- These are dedicated services that execute the actual build tasks. They compile code, run tests, and produce artifacts as part of the CI/CD process.
Each CI process is encapsulated into a project, where the basic flow is as follows:
- Choose VCS roots: choose the VCS roots that should trigger a new Teamcity build when new code is pushed up to those repos.
- Add build configurations: either manually create build configurations or write Kotlin DSL config-as-code to define individual jobs, and then the project defines the pipeline/build chain to run.

NOTE
It is possible for the server and an agent to coexist on the same computer, but for production purposes, we recommend installing them on different machines for a number of reasons, the server performance being the most important.
Teamcity server
The TeamCity server is a central management component that is usually installed on a single dedicated machine that manages all pipelines.
It does not perform any build or test actions directly; it orchestrates the process of using any amount of Build Agents to run these tasks.
NOTE
You can scale up TeamCity servers via a load balancer, to also assign more build agents in total by adding more servers.
Build agents
A Build Agent is a service that is installed on separate servers (Windows, Linux, or any Linux-based OS) to carry out various build-related tasks. TeamCity itself does not compile code but relies on Build Agents for this purpose.
The Build Agent service can be installed either on the same server as the TeamCity server or on different servers, and in any fashion. It basically just has to be a recognizable process running on an exposed port.

IMPORTANT
However, installing on a separate server is recommended to avoid limitations that can arise if the TeamCity server needs to be reset or if issues occur with the Build Agent.
- Installation: SSH into another VM and run the build agent as a docker container or install it directly on the VM and start it.
- Configuration: After installation, the Build Agent must be configured. This includes setting up the necessary tools and SDKs required for building your specific code, such as .NET SDK, JDK, PHP, etc. Essentially, the Build Agent acts as a local environment where all the build and compile processes occur.
- Execution: Once agents are set up, TeamCity can assign builds to them. The Build Agents check out the source code, compile it, and produce packages, thereby facilitating continuous integration and continuous delivery (CI/CD) workflows.
A very important thing to understand is that TeamCity automatically looks at which build agent to use for which task, depending on the type of software and packages that each build configuration needs.
For example, a build configuration that uses NPM heavily will only use a build agent that has NPM and Node installed.
TeamCity will understand how to choose that agent automatically by seeing if it has NPM installed via configuration management.

NOTE
On the free tier, you're only allowed to associate max 3 build agents per TeamCity server.
Why Teamcity
TeamCity is the most flexible CI provider ever because it works with any software and source code repository.
It also has these capabilities:
- build chains: ability to create dependencies between builds, able to create a graph so jobs/builds run in a predetermined order.
- configuration as code: offers a Kotlin DSL that is a configuration as code drop-in for manually creating pipelines.
- personal builds: from your local environment, you can create personal builds for a build configuration that only you can see, allowing you to test your builds locally. You can do this via an IntelliJ plugin.
Teamcity installation and setup
Local development to Teamcity server on Cloud
- Install from EXE
- Install TeamCity server, but not the build agent
- Connect to a remote TeamCity server, put in the one your company gave you

Creating cloud build agents
If you want to self-host build agents on the cloud by putting the build agents on EC2 instances so they don't interfere and hog RAM from the Teamcity server, you can do so by following these steps:
- Create EC2 instances, grab the key pairs
- In Teamcity, create a new cloud profile and fill out the AWS connection settings via your AWS access key

- Specify an EC2 image tempalte ot use for launching new EC2 isntances that will then be used as build agent VMs

TeamCity projects
A project is a container for templates, subprojects, build configurations, and version control connections.
- build configuration: a set of build steps or a Kotlin code-as-config file that defines the steps and instructions for building and packaging a project.
- template: A kotlin file or teamcity construct that is a used as a template to create build configuration files with DRY philosophy.
- subproject: a subfolder within a project that contains its own scoped build configurations and templates, mainly used for organization purposes.
- versioned settings: connects your Kotlin config as code from a remote git repo to be used to create all the build configs, templates, and subprojects of a project.
Project
│
├── VCS Root
│
├── Build Configuration
│ ├── Parameters
│ ├── VCS settings
│ ├── Build Steps
│ ├── Triggers
│ ├── Failure Conditions
│ ├── Build Features
│ └── Dependencies
│
├── Build Configuration
│
└── Template
Here are the important rules to understand about projects #tc-project-rules : ^a54b92
- All projects inherit from the root project.
- All build configurations, templates, and VCS settings within a project are available for use to any other component within that project or any subprojects of that project.
In TeamCity, child projects inherit many settings and entities from their parent, such as connections and cloud agent profiles.
The Root project lets you take advantage of this concept and define server-wide resources.
For example, you can create AWS cloud profile that spawns cloud agents accessible to all projects on the server.
NOTE
Note that since user permissions are project-based, only Root project administrators can edit its settings.
VCS root
A VCS (Version Control System) root is a crucial component in a CI/CD pipeline, specifically in TeamCity. It essentially acts as a connection point between TeamCity and your source control system, allowing builds to access source code for various projects or build configurations.
NOTE
A VCS root defines where TeamCity should look for the source code. Each VCS root can point to a specific repository in systems like Git or SVN.
VCS roots can be set at different levels:
- Project level: This is the most common approach, where you can configure a VCS root for a specific project. All build configurations under that project will inherit this VCS root.
- Root project level: While you could define a VCS root at the root project level for global access, it’s less flexible. Different teams can have different repositories, so it’s often more effective to define them at the project level.
VCS roots store the following information:
-
Fetch and push URLs that TeamCity uses to pull and push remote files.
-
Branch information: the list of repository branches TeamCity should track and which branch is the default (main) one.
-
Authentication settings: credentials TeamCity uses to access a repo.
-
Checkout settings: specify how remote files should be stored and whether submodules should be checked out along with the main repository.
-
Custom changes polling settings that allow you to override the default 60-second interval.
There are three important VCS root properties:
- VCS provider: The type of version control system supported by TeamCity. For example, Git, Perforce, Subversion, and more.
- VCS root name: The unique human-facing name of VCS root across all VCS roots of the project. This is the public name shown in TeamCity UI
- VCS root ID: Unique ID of VCS root across all VCS roots in the system. By default, the root ID combines truncated names of its parent project and the root itself, divided with an underscore.
- For example,
MyProject_HttpsGitHubComJohndoeMyrepoRefsHeadsMain. - When changing the root name, you can click the Regenerate ID link to update this value.
- For example,
- VCS root URL + authentication: The URL of a VCS repository. Supports URLs in different formats, like:
http(s)://,svn://,ssh://git@,git://and others as well as URLs in Maven format.
Connecting to VCS roots
When setting up a VCS root, credentials are required for accessing the repository, and you have many different ways to authenticate:
- username and password: weakest form of auth, where you log in using the credentials of the Teamcity User on Gitlab that you created.
- SSH: use an SSH key pair, where you give the public key to Gitlab and the private key to Teamcity, then TeamCity takes care of the work of giving the build agents all those private keys.
- refreshable access token: short-lived tokens acquired by TeamCity from a required VCS provider via existing OAuth connection

Connecting via SSH
To connect via SSH, you need to upload a private key in the OpenSSH format to Teamcity.
IMPORTANT
It's advisable to use SSH keys for more secure access instead of username and password, as it is more reliable and doesn't break if credentials change.
If using an SSH URL, you need to provide a private key so that TeamCity could access a repo. See the SSH Keys Management article or watch our video tutorial to learn more.
Here are the available private key options:
-
Uploaded Key — select this option to utilize the key(s) uploaded to the project.
-
Default Private Key — select this option to utilize the keys available on the file system in the default locations used by common ssh tools: the mapping specified in
<USER_HOME>/.ssh/configif the file exists or the private key file<USER_HOME>/.ssh/id_rsa(the files are required to be present on the server and also on the agent if the agent-side checkout is used). -
Custom Private Key — supported only for server-side checkout. Fill the Private Key Path field with an absolute path to the private key file on the server machine. If the key is encrypted, specify the passphrase in the corresponding field.
Connecting via refreshable tokens
if a VCS root that fetches data from a GitHub, GitHub App, Bitbucket Server, Bitbucket Cloud, Azure DevOps, GitLab, or JetBrains Space was configured using a TeamCity connection, refreshable tokens are enabled by default.
Refreshable access tokens are short-lived tokens acquired by TeamCity from a required VCS provider via existing OAuth connections (as opposed to static PAT tokens issued manually by users on a VCS hosting side).
Here's how it works when a build runs:
- TeamCity asks GitHub for access.
- GitHub issues a short-lived token.
- TeamCity clones the repository.
- When the token expires, TeamCity gets a new one automatically
Additional VCS root properties
Here are two additional VCS root properties:
minimum polling interval
Specifies how often TeamCity polls the VCS repository for VCS changes. By default, the global predefined server setting is used that can be modified on the Administration | Global Settings page. The interval time starts as soon as the last poll is finished on the per-VCS root basis. Here you can specify a custom interval for the current VCS root.
Some public servers may block access if polled too frequently.
If TeamCity detects that a VCS commit hook is used to trigger checking for changes, this interval is automatically increased up to the predefined value (4 hours). If the periodical check finds changes undetected via the commit hook, the polling interval is reset to the specified minimum.
Which project the VCS root belongs to
Due the the rules of projects, a VCS root in a project is available to all sub-components of that project.
You can move a VCS root to a parent project so that it becomes available for all build configurations inside this new owner and its subprojects.
Project vs Build configuration VCS roots
Sections related to VCS roots are available in both project and configuration settings.

NOTE
However, configurations never own roots. You can "attach" a VCS root to a configuration, but roots are always stored in (owned by) projects.
Here are the rules of VCS roots and how they affect build configurations vs projects:
-
A VCS root can be attached to multiple configurations, meaning that multiple build configurations can access the same repository with the same auth and checkout settings.
-
A single configuration may have multiple VCS roots attached, which allows you to work with different repositories within one configuration.
-
Editing VCS roots affects all configurations that use it. When modifying VCS root settings, you have an option to duplicate this root and store updated settings in this new clone, keeping the original root unchanged.
- use case: This allows you to customize one build configuration but leave other configurations that share this root unaffected.
Adding a build configuration manually
Let's first do it manually:
- Create a VCS Root: Go to your project settings, then select "VCS Roots" and click "Create VCS Root."
- Select Type: Choose your VCS type, e.g., Git.
- Configure Repository URL: Enter the URL of your source code repository.
- Authentication: Set up authentication (e.g., SSH keys).
- Save: This links TeamCity with your source code repository.

- Add a Build Configuration: In the project settings, select "Build Configurations" and click "Create Build Configuration."
- Name it: Give your build configuration a descriptive name, e.g., "NodeApp_Build."
- Select VCS Root: Attach the previously created VCS root to this build configuration


- Add Build Steps: In your build configuration settings, create build steps to specify how to build and test your Node.js application, using Node.js commands like
npm installandnpm test.
NOTE
Whenever you create a build configuration, TeamCity creates a unique ID for that, which is used internally and which TeamCity recognizes as a job or run, and then you can use that in the teamcity API or teamcity CLI to programmatically fetch the info of those jobs.
Checkout rules
When teamcity pulls source code from a VCS root, it pulls down the entire repository.
Teamcity checkout rules allow you to change that and only checkout out certain filepaths and folders you want from the repo instead of everything.
VCS checkout rules allow to map repo paths to build agent paths in the filesystem, which is useful only if you want to change how the repo maps to the build agent filesystem when checking out the source code.
NOTE
When is this useful? For composability and reusability, when you only need one folder or file from a VCS root and not everything.
checkout rules answer two closely related questions:
- Which repository paths does my build need?: what do you specifically want to check out?
- Which repository path changes matter to this build?: if you don't checkout certain folders and files, then they don't contribute to the build, and thus any changes to those omitted files and folders should not register as a VCS change.
The general syntax of a single checkout rule is as follows:
- use
+for including and-for excluding. - If no rule is specified, all files are included, which is the default.
- To include all the files explicitly, use the
+:.rule. -
- If you don't enter an operator, it will default to
+:.
- If you don't enter an operator, it will default to
| Syntax | Explanation |
|---|---|
+:.=>AgentPath | Checks out the root into the Path directory on a build agent. |
-:VCSPath | Excludes VCSPath (the path must be a directory and not a filename). |
+:VCSPath=>. | Maps the VCSPath from the VCS to the build agent's default work directory. |
VCSPath=>NewAgentPath | Maps the VCSPath from the VCS to the NewAgentPath directory on a build agent. |
+:VCSPath | Maps the VCSPath from the VCS to the same-named directory (VCSPath) on a build agent. |
+|-: VCSPath [=> AgentPath]
Here's an example of adding a checkout rule to a VCS root:
- example 1: checkout everything except this one
documentationfolder, which now means that changes in the documentation folder will not affect any VCS changes or trigger anything.

- example 2: map folderpath from repo to another path on build agent:
-:originalpath => another/path
Checkout mode
The VCS Checkout mode is a setting that affects how project sources reach an agent. This mode affects only sources checkout. The current revision and changes data retrieving logic is executed by the TeamCity server, and thus TeamCity server needs to access the VCS server in any mode.
The checkout mode is configured on the build configuration's Version Control Settings page, in the Checkout Settings section (advanced settings).
TeamCity has the following VCS checkout modes:
- Prefer to check out files on agent: With this setting enabled, TeamCity will use the agent-side checkout.
- This is the default setting for the newly created build configurations
- Always check out files on server: The TeamCity server will export the sources and pass them to an agent before each build. Since the sources are exported rather than checked out, no administrative data is stored in the agent's file system and version control operations (like check-in, label or update) cannot be performed from the agent.
- Do not check out files automatically: TeamCity will not check out any sources automatically
- The build checkout directory will not be cleaned automatically, unless the directory expiration period is configured.
Versioned settings
Versioned settings store all properties related to project and build configurations, fetching those properties and settings from a remote repo with version control.
If you're using Kotlin DSL to configure TeamCity, you might create a repository that defines your settings, including VCS roots, through code. This allows for version-controlled configuration, facilitating easier management and deployment of CI/CD settings.
The key thing to understand is that Versioned Settings has two separate concerns:
- Where TeamCity loads its project configuration from (the DSL repo + branch)
- What branch the builds themselves run against
Kotlin DSL works at the project level, fetching info from a .teamcity/settings.kts entrypoint from a repository, via configuration of versioned settings.
- Does the project with enabled versioned settings remain editable?: You can choose whether a project can be edited via TeamCity UI (in this case TeamCity synchronizes edits made in the UI with remotely stored settings) or only by modifying settings files on the VCS side.
- Can I apply different settings for separate project branches?: Yes, different repository branches can store different project settings.
TIP
A TeamCity configuration can use separate VCS roots for importing DSL settings and downloading sources.
If your build project and DSL settings are stored in the same repository (not recommended for public repos accepting external contributions), you can reuse the same root.
Basic versioned settings
In TeamCity, the project administrator must ensure Project Settings → Versioned Settings has:
- Synchronization: enabled
- Settings format: Kotlin
- When build starts: use settings from VCS

- synchronization settings: You can choose one of the following options on this page:
- Use the same settings as in the parent project (default).
- Disable synchronization.
- Enable synchronization. In this case, you can also define which settings to use when the build starts
- project settings VCS root: which remote repo to grab the kotlin DSL from and use settings from a
.teamcity/settings.ktsentrypoint.
As soon as you enable settings synchronization, TeamCity commits the current project tree and server settings to the remote repository. If the target location already stores project settings, a warning pops up.
This warning allows you to choose whether TeamCity should:
-
import from UI: overwrite the settings in the VCS with the current project settings on the TeamCity server (only if the two-way synchronization is enabled).
-
import from code: import the settings from the VCS replacing the current project settings on the TeamCity server with those from version control.
Synchronization types
If synchronization is enabled, it can work in either two-way or one-way mode.
- two-way sync: The default mode is a two-way synchronization. This mode is enabled when the Allow editing project settings via UI option is checked.

- one-way sync: If you disable the Allow editing project settings via UI option, the project settings become read-only in the UI and only reflect changes made on the VCS side. This is convenient if you prefer defining project settings' as code or load settings from a read-only VCS branch.
NOTE
Before applying the newly checked-in settings, TeamCity validates them. If the validation fails (for example, when a build configuration references a non-existent VCS root or has duplicate ID), the current project settings are left intact and an error is shown in the UI.
two-way sync
Here are the main rules and properties of two-way sync:
- UI changes become patches: Each administrative change made to the project settings in the TeamCity UI is committed to the version control system as a patch.
- The author of the commited changes matches the TeamCity user who made related project edits.
- Config as code is source of truth: If the changes are applied on the VCS side (if a Kotlin or XML settings file is edited), the TeamCity server detects them and modifies the project on the fly.
one-way sync
If you disable UI editing, then you are in one-way sync mode, where the config as code is the absolute source of truth and you can only change the project settings through Kotlin DSL.
Synchronization with subprojects
Enabling synchronization for a project also enables it for all its subprojects with the default "Use settings from a parent project " option selected.
- TeamCity synchronizes all changes to the project settings (including modifications of build configurations, templates, VCS roots, and so on) except SSH keys.
- To exclude individual subprojects from the synchronization, switch them to Synchronization disabled mode.
separate VCS roots
NOTE
Project settings can be saved to the same repo that hosts application sources, or a completely separate repository.
The default location for TeamCity project settings is the .teamcity folder in the root of the same repository that stores the target project. Depending on your workflow specifics and business needs, this default setup might not be optimal for your team.
For example, teams working with monorepos where stand-alone microservices and external libraries are hosted in adjacent directories would likely want to set up individual TeamCity projects that store their settings in separate directories. Using custom settings directories for each project ensures that projects targeting the same monorepo do not constantly override each other's settings.
Another scenario you might want to implement is moving TeamCity-specific files away from the sources. This approach obscures the specifics of your CI/CD ecosystem, hiding them from external parties. In addition, having a dedicated VCS repository that stores settings of your entire TeamCity server (each project has its own repository folder to store its settings) can also prove beneficial for settings maintenance and testing.
The Project settings VCS root selector allows you to choose which Configuring VCS Roots TeamCity should use to obtain and commit project settings.
TIP
You can choose any root owned by either this project directly, or by any of its parent projects.
This is due to the rules of projects (see [[#TeamCity projects]]), where all project components and subprojects inherit settings from the parent project, including VCS roots.
NOTE
Note that the Project settings VCS root combo-box does not allow you to create new roots. You need to navigate to the VCS Roots tab of your project settings and set up a required root before you can start using it on the Versioned Settings page.
Here are the general steps to set a VCS root to use for versioned settings for a project
- Basically, if you want to use a repo for versioned settings to set the settings for a project, that repo must be added as a VCS root for the project.
- Then in the versioned settings, you can enable synchronization, then select the Kotlin DSL repo to choose for versioned settings.

Custom settings path
The Settings path in VCS option allows you to manually specify a path to the directory that stores project settings.

NOTE
What makes this useful?: the main use case is to reduce Kotlin DSL repo duplication by having multiple different settings.kts files, and you choose which ones to use depending on the use case.
You can change the default .teamcity value to any custom path:
- example:
.teamcity-settings/accountingwill search for asettings.ktsfile in that directory. - example:
.refers to the root of the repo, which allows you to save settings directly to the repository root, looking for asettings.ktsat the root of the repo.
[!DANGER] Before committing a new revision of project settings, TeamCity clears the target settings directory.
- To prevent TeamCity from wiping important files, make sure this directory is not used for anything but project settings.
- Be extra careful when specifying
.as the settings directory: this value should only be used if you want a dedicated repository that stores TeamCity project settings and nothing else.
To prevent unexpected errors caused by ambiguous settings source, TeamCity does not allow you to change the settings path when the synchronization is already active.
To modify this settings path, disable the synchronization and save the settings, then re-enable it and specify the required directory.
NOTE
When you create new projects, TeamCity currently detects existing project settings only if they are stored in the default .teamcity folder. If project settings are stored in a custom repository directory, TeamCity does not offer options to import these settings or start a new project from scratch. As a workaround, do the following:
-
Create a new project from a remote repository.
-
Navigate to project settings and enable synchronization on the Versioned Settings page.
-
Specify the path to existing project settings in the Settings path in VCS field.
-
Click Apply to save your settings, then Load project settings from VCS... at the bottom of the page.
Defining settings to apply to builds
To specify which settings TeamCity should apply when a build starts, choose a required option on the Administration | <Project> Versioned Settings page.

When TeamCity needs to start a build, it can apply either of the two possible settings:
-
always use current settings — all builds use current project settings from the TeamCity server. Settings' changes in branches, history, and personal builds are ignored. Users cannot run a build with custom project settings.
-
use current settings by default — regular builds use the latest project settings from the TeamCity server. Users can run custom builds with settings imported from a VCS.
-
use settings from VCS — all branch builds and history builds, which use settings from VCS, load settings from the versioned settings' revision calculated for the build.
- Allows users to change configuration settings in personal builds from IDE or run a build with current project settings on the TeamCity server via the custom build dialog.
- With use settings from VCS, branch builds can load the versioned settings revision calculated for that build rather than unconditionally using the current/default project configuration on the server.
An ability to choose which of these two settings to apply grants you the following options:
-
multiple branch configuration: Have multiple branches with different settings in the project settings directory. This means your branch A can have parameters, steps, build features, artifact publishing rules and chain settings that differ from those in branch B.
-
personal builds: Start personal builds with changes made in the project settings directory, and these changes will affect the build behavior.
-
Add more flexibility to your history builds. TeamCity initially attempts to use the settings corresponding to the moment of the selected change. Otherwise, the current project settings will be used.
Using settings from VCS
[!DANGER] Certain settings cannot be loaded from a VCS. When TeamCity detects edits in these settings, it ignores them and applies current settings stored on the server instead. These settings are:
build triggers
build configuration-level options, such as hanging builds detection, personal builds' availability, build configuration type, and so on
clean-up rules
agent requirements in custom runs
settings of certain build features (for example, Commit Status Publisher uses VCS settings, while Pull Requests always use the current server settings)
edits to existing artifact rules (you can still add new and remove existing rules)
(only if the Apply Changes in Snapshot Dependencies and Version Control Settings setting is disabled) creating new and editing existing snapshot dependencies, checkout rules, and VCS roots.
seeing build configuration settings
Before starting a build, TeamCity stores a configuration for this build as a hidden artifact under the <project_settings_directory>/settings directory. You can inspect these configuration files to determine what settings were actually used by the build.
Adding a build configuration through Gitlab + Kotlin DSL
Here are the steps to set up Gitlab with Kotlin DSL to use config as code for defining everything within a project like build configurations, subprojects, and templates:
- To enable a Gitlab repo to push up Kotlin DSL to create a TeamCity project, you nned to modify the versioned control settings of a project to point to Gitlab.

- In your project repository, create a
.teamcitydirectory to hold Kotlin files. The create a Kotlin file (e.g.,build.gradle.kts) to define your project structure, including VCS roots and build configurations programmatically:
version = "2021.1"
project {
vcsRoot {
id("MyVcsRoot")
name = "My Git Repo"
url = "https://github.com/username/repo.git"
branch = "refs/heads/main"
}
buildType {
id("Build")
name = "Build Node App"
vcs {
root("MyVcsRoot")
}
steps {
script {
scriptContent = "npm install && npm test"
}
}
}
}
- Commit your Kotlin DSL configuration to the same repository. This allows versioning of your TeamCity configuration, enabling better management.
Adding parameters
If you have many build configurations that are similar or the only difference between them is the repo you're getting the source code from, then use teamcity parameters that you define at the project level or root level and then use that to dynamically read from those variables anywhere in the build config.
In teamcity, you can refer to parameters and read their values with the syntax below:
%parameter_name%
So here's an example where we want to create a dynamic repository parameter that we then set in teamcity project config:
- Create a new VCS root that uses the
repositoryvariable to dynamically define the Fetch URL of the repository

- Set the VCS root for the build configuration to the one we just created.

- Add a specific value for the recognized
repositoryconfiguration parameter in the settings -> parameters for the current build configuration:

- Edit parameter specs if necessary so you are prompted to provide the parameter values when creating the build configuration or running the build.

Subprojects
Subprojects help with organizing build configurations into individual subprojects but they also have granular control over permissions for who can run stuff in those subprojects.
SUbprojects appear like nested folders in a Teamcity server

Build configurations
Build configurations and pipelines represent actual CI/CD routines. A build configuration stores a sequence of build steps (basic operations to be performed during a build run), and settings required to execute these steps. These settings include:
-
parameters that allow you to quickly alter the configuration behavior;
-
triggers that allow TeamCity to automatically start new builds when certain conditions are met;
-
build features that extend the configuration's functionality;
-
agent requirements that allow you to run configuration builds on specific build agents;
Think of a build configuration as a collection of these three components working in tandem:
- VCS root: the source code repository to check out that the agent will run the build on
- build steps: the steps to run as part of the job on the VCS root codebase.
- trigger: when to run the build job, like whenever the VCS root gets a new commit.
Repository:
github.com/company/app.git
Steps:
./gradlew build
Trigger:
whenever Git changes
There are several problems with UI-only configuration:
Who changed the build?
What changed?
Can I review the change before applying it?
Can I reproduce this configuration on another TeamCity server?
Can I reuse the same configuration for 20 services?
Configuration as Code solves these problems by representing the configuration as source code.
Build configuration types
A TeamCity build configuration can have one of the following types:
-
Regular: most control, can publish artifacts as well as be within a build chain.
-
Deployment, which deploys artifacts of other builds to some external environment
-
Composite, which aggregates results from several other builds combined by snapshot dependencies and presents them in a single place
To switch a build configuration type, navigate to the Configuration settings | General tab.

Composite build configurations
Composite build configurations are "step-less" configurations designed to trigger multiple regular build configurations and track the results in a single place.
-
Composite configurations do not carry out any actual building routines.
-
Composite configurations aggregate all information from their dependencies and present them in a centralized manner.
-
Composite builds do not occupy build queue slots and do not require agents to run.
Deployment build configurations
When your project is built and tested, you often need to deploy it to its final infrastructure. For example, upload a package to the NuGet Gallery, deliver a container to a DockerHub repository, or update your documentation website sources. Different CI/CD solutions use different terms for this last step of a pipeline: "deploy" stage, delivery target, release, production, and so on.
In TeamCity, delivery tasks are performed by the same "build configuration" objects that carry out regular building routines. However, since building and delivery are different tasks triggered by different team members, a configuration that deploys a product can be explicitly marked as a Deployment Configuration.
- Deployment configurations do not differ from regular configurations in terms of functionality. They can utilize the same build features, employ same build runners, and so on.
- Splitting deployment and building/testing configurations into different subprojects is recommended to set up fine-grained user permissions.
Adding VCS roots
Attaching a VCS root to a configuration without repositories enables it to check out remote sources.
- Create a VCS ROOT
- Attach the VCS root(s) to the build configuration

Dealing with multiple VCS roots
All VCS roots download sources into the same checkout directory, meaning you have to be cognizant of how repos get mapped into the agent checkout filesystem when registering multiple VCS roots for the build configuration.
NOTE
To avoid file conflicts and keep the folder organized it’s best to download each repository’s sources into separate subdirectories using root checkout rules (see more in [[#Checkout rules]])
Here is an example of using checkout rules to avoid file conflicts between two VCS roots:
package _Self.buildTypes
import jetbrains.buildServer.configs.kotlin.*
object MultiRepoBuild : BuildType({
name = "Multi-repo build"
vcs {
root(MavenRepoRoot, "+:. => MavenRepo")
root(GradleRepoRoot, "+:. => GradleRepo")
}
})
VCS Triggers
VCS triggers in TeamCity are used to automatically initiate a build whenever there's a change in the associated VCS root (e.g., a commit or push to the source code repository).
NOTE
There is a big difference between a VCS trigger and version settings. Anything you push up in your Kotlin DSL version settings to a GitLab repo will automatically make the changes in the project but it will not create any runs. A VCS trigger is what actually triggers a pipeline: if the source code repo gets changed then the VCS root trigger activates, which is different than Kotlin DSL.
- You configure VCS triggers within a build configuration.
- Once a VCS trigger is set, TeamCity continuously monitors the linked VCS root for changes.
- When it detects a change, it queues a new build (you can configure these settings)
Creating a VCS trigger on a build configuration
- To achieve this we should add a Team City dedicated user to the GitLab repository
2. After selecting the build configuration, navigate to the "Triggers" tab and add a new trigger of type "VCS Trigger.":

- For the VCS trigger, specify the branches that should be listened to for the trigger. By default, all branches trigger the trigger, but you can filter it down to only specific branches like so:

TIP
For more info on the special syntax and what it means, check out [[#Teamcity artifacts]].
Trigger configuration
How triggers work is through a polling schedule.
In this specific case, Teamcity checks every 60 seconds if there is a new push to the gitlab repo that should trigger the configured VCS trigger, and if so, then run the project and its build configurations.
You can configure this VCS trigger behavior like so:
- quiet period: the polling interval. By default, this is 60 seconds
- branch filter: the branches to amtch on for the trigger
+:*-rc: matches all branches that end in "rc"-:*-test: don't match any branches that end in "test"

You can also add extra advanced trigger rules which include matching on the following:
- specific VCS root: you can configure multiple possible VCS roots for a project aand then add different trigger rules for them
- gitlab username: trigger or don't trigger depending on the user who pushed the branch
- comment regex: trigger or don't trigger depending on the commit message content regex matching.
- example use case: skip build on commit with content
[skip ci]
- example use case: skip build on commit with content

You can also exclude certain filepaths from being tracked, and therefore don't trigger builds if they change within the VCS root repo.

Triggers
Scheduled triggers
Scheduled triggers let you run build configurations on a cron schedule.

Branch remote run trigger
The branch remote run trigger automatically starts a new personal build each time TeamCity detects changes in particular branches of the VCS roots of the build configuration. Finished personal builds are listed in the build history, but only for the users who initiated them.
A trigger monitors branches with names that match specific patterns.
Default patterns are:
- for Git repositories:
refs/heads/remote-run/*
Following this convention, here is how to trigger a remote build
% cd <your_local_git_repo>
% git branch
* master
% git checkout -b my_feature
Switched to a new branch 'my_feature'
//code, commit; code, commit
% git push origin +HEAD:remote-run/my_feature
dealing with multiple VCS roots caveat
If your build configuration has more than one VCS root which support branch remote run, and you push changes to all of them, TeamCity will start one personal build with changes per each VCS root.
Build steps
Setting step execution conditions
Build steps run top to bottom, in the order they appear in the UI or configuration code. By default, TeamCity runs all steps until one fails. If a step fails, the build is marked as failed and remaining steps are skipped.
You can override this behavior in build configurations by setting a custom execution policy that defines when a step should run.

Execution conditions consist of two parts:
-
A general rule selected from a corresponding drop-down menu.
-
Optional additional conditions added via the Add condition menu.
general rules
-
Only if build status is successful — before starting the step, the build agent requests the build status from the server, and skips the step if the status is "failed". This considers the failure conditions processed by the server, like failure on test failures or on metric change. Note that this still may be not exact as some failure conditions are processed on the server asynchronously (TW-17015).
-
Only if build status is failed — same as above, but the agent skips the step if the build status is "success". This condition is useful for rollback or cleanup actions.
-
If all previous steps finished successfully — runs if all prior steps on the agent succeeded, without checking the build status reported by the server.
-
Even if some of the previous steps failed — runs regardless of prior step results or build status.
-
Always, even if build stop command was issued — ensures the step runs even if the build was canceled. For example, if two steps use this setting and the build is stopped during the first, the second will still execute. A second stop command fully terminates the build.
**additional conditions
You can refine execution behavior with custom conditions.
Execution conditions make builds more flexible and address many common use cases, such as:
-
running the step only in the default branch
-
running the step only in the
releasebranch -
skipping the step in personal builds

Alternatively, select the Other condition option to add the parameter-based execution condition, which is a logical condition that takes on input any build parameter provided by the TeamCity server or agent.
For example, to run the build step only on the testbranch branch, you can test the value of the teamcity.build.branch parameter, as follows:

Step status parameters
TeamCity provides teamcity.build.step.status.<step_ID> parameters that report the status of the step with the given ID. These parameters can be used, for instance, for crafting fine-grained execution conditions.
Step IDs are shown below their names, and are editable only when you create them.

The available values of the teamcity.build.step.status.<step_ID> parameters are:
-
success— when a step finishes with no errors. -
failure— when a step failed. This status is reported even when all build problems were muted. -
cancelled— when a build was cancelled while this step was running.
NOTE
The teamcity.build.step.status.<step_ID> parameters appear only after their corresponding steps finish, and are not available right from the moment a build starts. This means neither steps that are still running, nor skipped steps have their teamcity.build.step.status.<step_ID> parameters available.
Build configuration artifacts
Artifact rules in TeamCity are used to organize build outputs into a specific directory structure and store them in a TeamCity artifact filesystem unique to the build configuration, where under the hood those artifacts are stored in S3 or something.
For a build configuration, you have two important settings when it comes to artifacts:
- publish artifacts: when to publish artifacts. You have these options:
- even if build fails: even if build fails, publish artifacts
- no publish on fail: if build fails, don't publish artifacts
- artifact paths: provide Team City-specific syntax for describing the source code artifact path mapping to the Team City build agent runner environment target directory.

Artifact path syntax
Two Golden Rules to Remember:
- Rules are Additive: TeamCity reads them one by one. If you include everything in one line and exclude specific files in the next, TeamCity follows both instructions.
- You Can't Rename: If you try to "rename" a file, TeamCity ignores the rename and just creates a new folder with that name instead. It is strictly for organizing into folders.
Here are the basic rules:
- Include/Exclude Logic: Rules start with
+:to include files or-:to exclude them - Source and Destination: The syntax follows a
source => destinationformat- Source: The pattern or path of files from your build agent. This is the folder on your build agent where the files currently live.
- Destination: The target directory structure you want to create within the build's artifact folder.
- This is the name of the folder you want to create in TeamCity.
- Using a dot
.just means the "root" (the main artifacts folder).
- Wildcards: You can use
*to match files in a directory or**to match files recursively through subdirectories
+: source => target // to mount source code path to target path
-: source // to ignore a source code path
Let's go more in depth into the language:
+: include-: exclude*: star glob pattern**: recursive star glob pattern
simple example
Here's a simple example:
- Rule:
+: data/* => results - Translation: "Take everything (
*) inside thedatafolder and put it into a new folder calledresultswithin the artifacts folder of the build configuration in TeamCity."
So this below:
+: **/* => target_dir
-: **/folder1 => target_dir
maps all files in the source code to the teamcity build runner filesystem but then removes/ignores folder1.
complex example
Let's dissect every single artifact syntax rule from this example:

calculator-service/artifacts/*: takes every file within the calculator-service/artifacts folder and adds it into the root artifacts folder.
NOTE
From this we learn that if you don't use the plus or minus syntax, then by default it's additive syntax and it's adding it to the root of the artifacts folder file system for the build configuration.

What about the addition of an additive rule and a subtractive rule?
+:calculator-service/artifacts/* => another_directory
-:calculator-service/artifacts/*.log => another_directory
These rules say to map every file in the calculator-service/artifacts directory and put it int the /another_directory folderpath in the artifacts filesystem, but exclude all files matching */log (exclude log files).

Build numbers
Build numbers are useful for versioning artifacts created by TeamCity, which is essential when moving to Octopus Deploy.
NOTE
Here's the main idea: add a build counter to create a build number string, then dynamically use that build number to add versioning to the naming convention of your build artifacts produced by Teamcity.

- Add a build counter variable, which starts at 1, then automatically increments each time TeamCity runs the build configuration and creates a new build.
- In the build number format, use the build counter variable via the TeamCity-managed
build.counterparam, which reads the build counter variable value for dynamically setting the value of the build number. - When creating artifacts in teamcity, it's useful to have these three parameter configurations for dynamic naming:
repository: a custom param you set to specify the gitlab repo name associated with the current build configuration.build.number: a TeamCity-managed param that retrieves the build number formatted string associated with the current build configuration.teamcity.build.branch: a TeamCity-managed param that retrieves the branch of the gitlab repo associated with the current build configuration.
Build features
Build features in a TeamCity project offer additional configuration for the build and let you do important side effects like auto-merging or other shit. I don't know.
- auto-merging: create a rule so that if a build succeeds, merge the source code branch into another branch like
main - commit status publisher: Create a rule so that you get Team City to automatically build on a pull request, and the pull request can only get merged if the build passes.
NOTE
Although build features are powerful, make sure to not overuse them because it will be confusing for other developers when they see build features do such things like change the content of the repository after a push. Only use build features that don't actually change anything in the source code.
Auto-merge
Here's what the below auto-merge thing example does:
- Watch for all branches except the
rc(release candidate) branch - Choose to perform the merge if the build is successful, specify to create a merge commit for that and with a specific message syntax.
- Merge the incoming branch into the
rcbranch

commit status publisher
- Specify the VCS root and the Teamcity Gitlab user that will be the one with access to the gitlab repo and able to do stuff like run pull request actions
- VCS root: the source code repository
- Publisher: the remote repository to add the integration to, like GIthub or Gitlab

- On your VCS root source code repo, push up some code, make a pull request, and a build will automatically start running.

Notifications
THe notifications build feature allows you to send an email or slack notification to someone after the build finishes.

Pull requests
Pull requests build feature allows you to run builds based on a pull request to the VCS root fo the build configuration.

You also have three additional filtering settings:
- filter by authors: only trigger builds on pull requests by a specific author(s)
- filter by source branch: only trigger builds on pull requests whose source branch match the branch syntax match pattern you provide to this textarea.
- filter by target branch: only trigger builds on pull requests whose target branch match the branch syntax match pattern you provide to this textarea.
Build approval
The Build Approval build feature allows users to manually control the build start by using approvals. This build feature ensures that builds will not start unless they are approved by individual users or groups defined in the approval rules.
If a build is not approved within the specified period of time, it will be cancelled.
Instead of a build automatically continuing to the next step in a pipeline, TeamCity pauses and waits for an authorized person to explicitly approve or reject it.
Swabra
Swabra is a bundled TeamCity plugin that allows you to add the Swabra build feature to your build configurations. This build feature allows you to do the following:
-
Remove files generated during a build. The feature creates a list of all files in the checkout directory after the sources checkout is complete. After a build finishes (or before the next build starts), files that are not on this list are automatically removed.
-
Detect files modified or deleted during the build. Such files are reported to the build log (however, deleted files are not restored). This allows you to ensure your new builds do not start with certain source files deleted or modified by previous builds, and initiate a clean checkout if this is the case.
NOTE
Swabra is TeamCity's built-in Build Files Cleaner feature. Its job is to keep build agents clean and predictable by detecting or removing files that previous builds leave behind.
If you've ever had a build pass on one agent but fail on another because some old file was lying around, Swabra is designed to catch that kind of problem.
The issue with leaving stale checkout out files on a build agent is that leftover logs, caches, and build outputs can cause flaky builds, stale artifacts, and false positives.
Here's the main principle behind how Swabra works:
Swabra tracks what existed immediately after source checkout and identifies what was added, modified, or deleted later
- When teamcity checks out a VCS root, swabra makes a snapshot
- After the build configuration finishes, swabra makes another snapshot
- Swabra compares the before build and after build snapshots to see what changed.
It can then:
- remove new files
- report modified files
- report deleted files
- force a clean checkout if the workspace is no longer trustworthy
IMPORTANT
Swabra is compatible with any build configuration regardless of its build steps. However, it should be used only when the checkout mode is set to automatic checkout because when configured, Swabra runs before the first build step to record the file tree after the sources checkout and to restore it after the build finishes.
File content replacer
The File Content Replacer build feature is essentially TeamCity's built-in way to modify text files immediately before a build runs and then restore them afterward.
Think of it as:
Checkout source
↓
Modify file(s)
↓
Run build
↓
Restore original file(s)
NOTE
Why is this useful?
This lets you inject build-specific values without permanently committing them to Git, which is useful in the case of injecting secrets at runtime.
The feature runs before the first build step and requires Automatic Checkout mode. TeamCity:
- Finds matching files.
- Searches for text using a regex or fixed string.
- Replaces matching content.
- Runs the build.
- Restores the original contents afterward
Here's an example:
Imagine we want to replace the hardcoded version with the %build.number% parameter.
[assembly: AssemblyVersion("1.0.0.0")]
Find:
1.0.0.0
Replace:
%build.number%
Here are the steps to do so:
- Specify the file patterns to search via Ant-style wildcards
- Specify the specific regex pattern to match on
- Specify what to replace the text with
Here's an example in Kotlin DSL:
features {
replaceContent {
fileRules = "**/*"
pattern = "(?iu)the\h+pattern\h+to\h+search\h+for"
regexMode = FileContentReplacer.RegexMode.REGEX_MIXED
replacement = """%\teamcity.agent.work.dir%\nd_r\bin\isf"""
}
}
Build chains
You can consider build configurations within a project as individual pipelines/jobs, and then if you want to do what github actions does in parallelizing and adding jobs as dependencies of each other, then you can look to build chains, where you can create a directed dependency graph of builds that depend upon other builds.
In teamcity, you can specify two behaviors when it comes to build chains:
- sequential execution: specify that a build needs another build to finish, so it executes sequentially after.
- parallel execution: specify that a build can run in parallel with another build.
Dependencies
- snapshot dependencies — ordering + same source revision
- the mechanism that forms build chains and synchronizes source revisions between dependent builds
- artifact dependencies — passing files between builds
- Artifact dependencies solve a different problem: transferring files produced by one build into another.
Snapshot dependencies tell TeamCity that two build configurations should operate with synchronized revisions.
Imagine commits:
A --- B --- C --- D
A chain begins for commit C.
Without a coherent chain, you don't want:
Compile → commit C
Test → commit D
because then you're testing something different from what you compiled.
You want:
Compile ─────┐
├── revision C
Test ────────┘
That consistency is one of the major reasons snapshot dependencies exist.
Creating a build chain
You can create build chains where a build configuration depends on another build configuration sequentially by creating a new snapshot dependency and artifact dependency:
- Go to the dependencies settings of a build configuration and then add a new snapshot dependency:

- Configure the snapshot dependency

- Add a new artifact dependency so the dependent build configuration gets access to the artifacts of the previous job.

Agent requirements
On each build configuration, you can set the agent requirements for the build configuration, which restricts the build configuration to only be compatible with agents that pass the criteria you set:

You can add a requirement like so:

Failure conditions
Failure conditions allow you to add specific conditions to decide when a build should fail.
- Go to build configuration settings, then go to failure conditions
- Add a new failure condition

Feature branches
Feature branches allow you to create builds on different branches of your VCS root, not just main.
But first, you have to enable feature branches by editing the branch specification of the VCS root you're going to use for versioned settings to allow for other branches to be recognized and triggered on.


For more info on branch specification syntax, go here:
Logical branches
It's important to understand what logical branches are in Teamcity:
Suppose your source root has:
Default:
refs/heads/main
Branch specification:
+:refs/heads/*
Then
refs/heads/main → main
refs/heads/test-thing → test-thing
refs/heads/foo → foo
Those right-hand values are TeamCity's logical branch names.
IMPORTANT
Here are two extremely important rules:
- TeamCity explicitly groups branches from multiple VCS roots according to their logical branch names.
- If another root doesn't contain that logical branch, its default branch can be used instead
NOTE
when a logical branch exists for one VCS root but not another, the root missing that branch uses its default branch
Templates
Templates allow you to quickly spawn multiple configurations with identical settings. After a configuration is created, you can override its settings.
- Create a template manually in the TeamCity UI or extract the template from an existing build configuration
- When creating a build configuration, you can create it from a template or you can base it on a template like so:

Build steps
Recipes
Recipes are custom configuration build steps that do not ship with TeamCity. Project administrators can add recipes by doing the following:
-
Extract a recipe from a regular build step.
-
Download a recipe created by TeamCity developers or community from the JetBrains Marketplace.
This section allows you to control whether the second option is available.
Related article: Working with Recipes
SSH build steps
You have several available build steps that let you use ssh to connect to a remote server and perform SFTP or RPC on those servers.
Here's the different types of ways you can do a build step that uses SSH somehow:
- command line build step: A vanilla command line build step where you construct an
sshbash command. - SSH exec step: executes a command after remotely connecting to a server via SSH. You define how to authenticate with the remote server, either by uploading a private key to the build configuration, or using Teamcity SSH agent.
- SSH file transfer step: remotely connects to a server via SSH and handles file transfer via SFTP. You define how to authenticate with the remote server, either by uploading a private key to the build configuration, or using Teamcity SSH agent.
SSH exec example
- Upload an SSH private key pair, name it.

- Add an SSH exec build type to your build configuration

Teamcity pipelines
TeamCity Pipelines is a CI/CD server designed to integrate seamlessly into a developer's workflow. It offers a visual pipeline editor that allows users to configure pipelines without needing to manually manage complex YAML files, though YAML configuration is also supported.
Key features include:
- Intelligent Configuration Assistance: The tool provides suggestions based on the build steps you define, such as optimizing Maven packages for test result collection or parallel execution.
- Environment Flexibility: Pipelines can run on various build agents, including Linux, macOS, and Windows.
- Smart Optimization: It includes features like job reuse, build caching, and easy parallelization for tests, which can be configured via simple sliders or buttons to reduce overall build time .
- Integrated Debugging: Users can view build logs, visualize pipeline progress, and even connect to a terminal on the running build agent for troubleshooting, all within the same interface.
- Configuration as Code: Pipelines support both YAML and Kotlin DSL, allowing you to define your pipeline structure as code that can be fully branched
Teamcity connections
A TeamCity connection is an entity that stores settings required to access resources on a 3rd-party service: a VCS hosting, a cloud hosting provider, an image registry, and so on. This section allows you to create connections available to all subprojects and build configurations owned by this project.
Related article: Configuring Connections
TeamCity connections store credentials required to access external services. Based on a type of this 3rd-party service, are two major connection categories.
- VCS connections: These connections store the information needed to access VCS providers. They offer the fastest way to create projects, build configurations, and pipelines — handling authentication automatically so you can simply choose a repository and start configuring your build steps.
- Without a configured connection, you would require to provide credentials for every new pipeline, build configuration, or root.
- Non-VCS connections: Connections in this category store information needed to access services other than VCS providers and are used for purposes unrelated to retrieving remote repository sources. For example:
- Jira connection allows TeamCity builds to show links to Jira issues in their Changes tabs, and post build statuses back to these issues.
Manually without connections: Teamcity + Gitlab SSH keys
NOTE
Why should we use SSH keys to connect Team City to a Gitlab VCS root? Because it removes the need for a username and password by having a direct SSH connection, we can avoid credentials being leaked.
Here's the grand overview for how we'll achieve this:
- Create SSH key pair: give the public key to Gitlab and the private key to Teamcity.
- Configure the connection: edit the VCS root to use SSH instead of standard HTTPS authentication with GitLab.
SSH keys for connecting to a GitLab repo from Team City live on the project level. For each project it needs its own individual SSH key pair to connect to a certain VCS root or multiple VCS roots.
Here are the steps:
- Create the ssh keys in the
.pemformat, which is what TeamCity requires:
ssh-keygen -f teamcity -m 'PEM'
- Uplaod the private key to Teamcity

- Upload the public key to GIthub (should end in
.pub)

- When editing the VCS root, make sure to change to the SSH
<user>@<host>syntax for specifying which repo to connect to for the VCS root:

- Change the authentication method to use SSH keys and specify the specific SSH key you set at the project level

VCS connection
For most modern TeamCity setups:
- Create a GitHub App (or other OAuth) connection.
- Avoid storing PATs where possible.
- Let TeamCity create refreshable tokens automatically.
- Reuse tokens through the VCS Auth Tokens page.
- Limit project and repository scope where practical.
To create a new connection, do the following:
-
Open project settings and navigate to the Connections settings tab.
-
Click Add Connection. Note that connections can be used only in their parent projects and their subprojects. If you want a connection to be available globally, add it to the Root project.
-
Select the connection type, set its Display name to distinguish it from the others, and configure it as described below.

Projects from connections
You often don't need to create tokens manually.
When you create a project from a configured GitHub/GitLab connection, TeamCity automatically:
- Creates the token
- Assigns it to the VCS Root
- Uses it for authentication
API and automation use case
If you're creating projects and VCS roots through:
- TeamCity Kotlin DSL
- TeamCity REST API
- Automation scripts
Github app
GitHub App connections expose an endpoint that can generate token IDs programmatically. This allows automation to obtain auth tokens without using the UI.
/app/oauth/githubapp/installationToken
TeamCity GitHub App connections can issue authentication tokens that let TeamCity access GitHub repositories. This usually happens automatically when you create projects in the UI or manually issue tokens on the VCS Auth Tokens page.
The <TeamCity_Server_URL/app/oauth/githubapp/installationToken endpoint provides an alternative way to generate these tokens without using the UI. This is especially useful when configuring build configurations and VCS roots via the REST API, as it allows you to programmatically obtain the token IDs needed for those roots.
See this article for more information: InstallationToken Endpoint: How to Issue GitHub Auth Tokens Programmatically.
Gitlab auth
Refreshable access tokens
A refreshable access token is TeamCity's modern way of authenticating to VCS providers more securely than connecting with username/password or PAT tokens.
Instead, TeamCity uses an existing OAuth connection and automatically obtains and renews access tokens when needed.
Think of it like:
- PAT = giving TeamCity a permanent key.
- Refreshable Token = giving TeamCity permission to ask the provider for a new temporary key whenever it needs one.
Here are the main benefits:
- ✅ More secure than username/password authentication.
- ✅ No need to manually rotate expired PATs.
- ✅ Centrally managed in TeamCity.
- ✅ Can be restricted to specific projects.
- ✅ GitHub App tokens can even be restricted to specific
Many TeamCity features need access to your Git provider:
- VCS Roots
- Commit Status Publisher
- Pull Request integrations
- Project creation from Git repositories
- Other VCS-related features
Instead of each feature storing its own credentials, TeamCity can share a refreshable token across many projects and build configurations:
In many organizations you'll see:
Root Project
└─ GitHub App Connection
└─ Refreshable Token
↓ inherited by
Team A Project
Team B Project
Team C Project
This avoids every team creating its own PATs. The connection and token are managed once and inherited by subproject.
Token scope
You can scope a token to certain actions:
- project scope: scope a token to only certain projects.
- repo scope: For GitHub App connections, TeamCity lets you limit a token to specific repositories.
project scope
Generated tokens are by default available for the currently edited project and all of its subprojects.
NOTE
That is, if you create a token from the Root project's VCS Auth Tokens page, this token can be used by any project on the server by default.
You can view projects for which a token was initially configured under the Project Scope.

repository scope
GitHub App connections support granular per-repository token permissions. When creating a refreshable token using this connection, you need to specify which repositories this token can access. Enter repository names without user/organization names (myRepo instead of myUser/myRepo).
Creating a refreshable token
Think of a Connection as:
"TeamCity knows how to talk to GitHub/GitLab using OAuth."
The refreshable token is then created from that connection.
- Create a connection: Before you can create refreshable tokens, you must configure a Connection in Project Settings → Connections.

- create a token: login as a user with the Project Administrator role to create a token, specifying the token name, connection to use, and optional project scope.
Project Settings
→ VCS Auth Tokens
→ Generate New Token

- Assign It to a VCS Root or Build Feature: When configuring a VCS Root or build feature, you can specify the authentication method to either generate a new token or use an existing token from the VCS auth tokens page.


Users, groups, and roles
- users: represent individual users in a Teamcity server with individual permissions
- group: represent groups of permissions just like user groups in AWS, where we can assign permissions to the group, and then users assigned to that group will gain the permissions of the group.
- roles:
Roles
Roles are disabled by default.
To enable roles, you must enable the per-project permissions.
Here’s how roles typically function in CI/CD systems like TeamCity:
-
Role Creation: Roles are created to group specific permissions. For example, a role may be labeled as "Project Developer," granting access to develop and manage project configurations.
-
Assigning Permissions: Once a role is created, you can assign various permissions to that role. For instance, you may assign permissions to run builds or manage specific project configurations.
-
User Assignment: Users or groups can then be assigned a particular role. This means that every user with this role will inherit the permissions it grants.
-
Project Level: Roles are generally assigned at the project level, allowing you to manage access effectively. For example, one user may be assigned the role of "Developer" for a .NET project while another may have restricted access on a different project.
Roles help in maintaining security and appropriateness within project teams, ensuring that users only have access to the areas necessary for their tasks.
Access tokens + API
Creating an access token
Here's how to create a teamcity access token:
- Create an access token

- Select permissions type, like read-only.
Teamcity MCP
This is how the teamcity configuration should be set up:
{
"mcpServers": {
"teamcityMCP": {
"url": "<instance_url>/app/mcp",
"headers": {
"Authorization": "Bearer <teamcity_access_token>"
}
}
}
}
- Then replace
<instance_url>with your specific self-hosted TeamCity instance origin likehttps://ci.compusearch.com. - Grab an access token so you can attach it as bearer auth for the
Authorizationheader for authorizing with the remote MCP server.
TeamCity CLI
Basics
Installation
# macOS / Linux
brew install jetbrains/utils/teamcity
# via a bash script
curl -fsSL https://jb.gg/tc/install | bash
# Windows
winget install JetBrains.TeamCityCLI
# via a powershell script
irm https://jb.gg/tc/install.ps1 | iex
# Connect to your server
teamcity auth login https://example.teamcity.com/
macOS (Homebrew):
brew install jetbrains/utils/teamcity
Linux:
curl -fsSL https://jb.gg/tc/install | bash
Windows (Winget):
winget install JetBrains.TeamCityCLI
npm:
npm install -g @jetbrains/teamcity-cli
Setup
- Login
teamcity auth login
Read-only mode
To enable read-only mode, set TEAMCITY_RO=1 as an env var, or run teamcity config set ro true:
teamcity config set ro true
Authentication and automation
Basic auth
teamcity auth login: login to teamcity interactively with OAuth 2.0teamcity auth lgout: logout of teamcityteamcity auth status: get auth status
Authentication with PAT
Here is how to do automated authentication with a single command, which requires two main pieces of information:
- teamcity self-hosted server URL: your specific Teamcity self-hosted server URL
- teamcity token: a teamcity PAT you created
teamcity auth login --server https://teamcity.example.com --token <token> --no-browser
Here are the additional flags you can set when using teamcity auth login:
--server <server-url>: sets the self-hosted server URL of your teamcity instance you want to connect to--token <token>: uses PAT for auth with teamcity--no-browser: no interactivity with browser, only in CLI, best for automation.
Teamcity environment variables
Environment variables override configuration file settings and are the recommended way to configure the CLI in CI/CD pipelines.
| Variable | Description |
|---|---|
TEAMCITY_URL | TeamCity server URL. Takes precedence over default_server in the config file. |
TEAMCITY_TOKEN | Access token for authentication. Takes precedence over the keyring and config file token. |
TEAMCITY_GUEST | Set to 1 to use guest authentication (read-only, no token needed). The CLI must be able to resolve the server URL (via TEAMCITY_URL, DSL detection, or the config file). |
TEAMCITY_RO | Set to 1, true, or yes to enable read-only mode. When enabled, all non-GET API requests (POST, PUT, DELETE) are blocked, preventing any modifications to the TeamCity server. Useful for monitoring scripts and dashboards. Can also be set per server in the config file with ro: true. |
TEAMCITY_DSL_DIR | Path to the Kotlin DSL directory. Overrides automatic detection of .teamcity/ or .tc/ directories. |
NO_COLOR | Disable colored output. Follows the NO_COLOR standard. |
TEAMCITY_NO_COLOR | App-specific alternative to NO_COLOR for disabling colored output. |
TEAMCITY_NO_UPDATE | Set to 1, true, or yes to disable automatic update checks. Update checks are also disabled automatically in CI environments and non-interactive terminals. |
TEAMCITY_HEADER_* | Add an HTTP header to every outgoing request. The suffix becomes the header name with underscores converted to hyphens, canonical-cased: TEAMCITY_HEADER_FOO_BAR=baz sends Foo-Bar: baz. Empty values are ignored; values containing CR/LF/NUL are dropped to prevent header injection. Header values are redacted in --verbose output. |
DO_NOT_TRACK | Set to 1, true, yes, or on to disable anonymous usage statistics. Follows the industry convention. Takes precedence over TEAMCITY_ANALYTICS and the config file. |
TEAMCITY_ANALYTICS | Set to 0, false, no, or off to disable anonymous usage statistics for this CLI specifically. Takes precedence over the config file. |
auth environment variables
When doing automation, it's important to set the these two environment variables:
TEAMCITY_URL: your specific Teamcity self-hosted server URLTEAMCITY_TOKEN: the teamcity token
$env:TEAMCITY_URL = "https://teamcity.example.com"
$env:TEAMCITY_TOKEN = "your-access-token"
set TEAMCITY_URL=https://teamcity.example.com
set TEAMCITY_TOKEN=your-access-token
Once you set these env vars, you can skip authentication and use the teamcity CLI directly:
$env:TEAMCITY_URL = "https://teamcity-prod.example.com"
teamcity run list # uses teamcity-prod
teamcity auth status # shows teamcity-prod
set TEAMCITY_URL=https://teamcity-prod.example.com
teamcity run list # uses teamcity-prod
teamcity auth status # shows teamcity-prod
readonly mode
To restrict the CLI to read-only operations (no builds triggered, no data modified), set TEAMCITY_RO=1.
set TEAMCITY_RO=1
Commands
Command primer
TeamCity CLI uses shorter names for TeamCity concepts. Knowing these mappings will help you navigate the commands.

The hierarchy is: Project contains Jobs, each Job produces Runs, and each Run executes on an Agent.
- project IDs: projects can be referenced by their project name
- job IDs: jobs (build configurations) can be specified either through their
<ProjectName>_<BuildConfigurationName>syntax identifier. - runs: runs (builds in a build configuration) have a numeric identifier visible in the UI.
list commands
IMPORTANT
Most commands expect IDs, not display names. Use teamcity job list or teamcity project list to find them.
view commands
Most view commands support a --web flag that opens the corresponding page in your browser:
teamcity run view 12345 --web
teamcity job view MyProject_Build --web
teamcity project view MyProject --web
The
--webflag works withrun view,job view,project view, andagent view.
output options
Both list and view commands come with two output modes you can set via flags
--plain: default, outputs info in plain text--json: outputs info as structured JSON
# JSON output (see Scripting and automation for details)
teamcity run list --json
teamcity run list --json=id,status,webUrl
# Plain text for scripting
teamcity run list --plain
teamcity run list --plain --no-header
teamcity run
teamcity run list: lists the 30 most recent teamcity runs--job <job>: list builds from a specific build configuration--status <status>: filter to get only builds of a specific status, like "success" or "failure"--limit <n>: only receive back maxnbuild records.
# Builds from a specific job
teamcity run list --job MyProject_Build
# Only failures from the last 24 hours
teamcity run list --status failure --since 24h
# Builds on a specific branch
teamcity run list --branch main --limit 10

teamcity run view <run-id>: provides detailed info of a specific runteamcity run start <job>: starts a build using a specific build configuration--watch: enables watch mode to follow the build in real time.--branch <branchname>: uses a specific branch of the configured project VCS root.
teamcity run list reference
| Flag | Description |
|---|---|
-j, --job | Filter by job (build configuration) ID |
-p, --project | Filter by project ID |
-b, --branch | Filter by branch name. Use @this to resolve the current git branch. |
--status | Filter by status: success, failure, running, queued, error, or unknown |
--favorites | Show favorite builds for the current user. |
-u, --user | Filter by the user who triggered the build. Use @me for the current user. |
--revision | Filter by VCS revision (commit SHA). Use @head to resolve the current git HEAD. |
--since | Show builds finished after this time (for example, 24h, 7d, 2026-01-21) |
--until | Show builds finished before this time |
-n, --limit | Maximum number of runs to display |
--json | Output as JSON. Use --json= to list available fields, --json=f1,f2 for specific fields. |
--plain | Tab-separated output for scripting |
--no-header | Omit header row (use with --plain) |
-w, --web | Open the list in the browser |
teamcity run start reference
| Flag | Description |
|---|---|
-b, --branch | Branch to build. Use @this to resolve the current git branch. |
--revision | Pin build to a specific Git commit SHA. Use @head to resolve the current HEAD; short SHAs are expanded from the local repo. |
-P, --param | Build parameters as key=value (can be repeated) |
-S, --system | System properties as key=value (can be repeated) |
-E, --env | Environment variables as key=value (can be repeated) |
-m, --comment | Build comment |
-t, --tag | Build tag (can be repeated) |
--personal | Run as a personal build |
-l, --local-changes | Include local changes. Accepts git (default), - (stdin), or a file path. |
--no-push | Skip auto-push of branch to remote |
--clean | Clean source files before building |
--rebuild-deps | Rebuild all dependencies |
--rebuild-failed-deps | Rebuild failed or incomplete dependencies only |
--reuse-deps | Reuse existing builds as snapshot dependencies. Accepts a comma-separated list of build IDs or can be repeated. TeamCity resolves which dependency slot each build fills by its build configuration. |
--top | Add to the top of the build queue |
--agent | Run on a specific agent (by ID) |
--watch | Watch the build after starting it |
-i, --interval | Refresh interval in seconds when watching (default: 3) |
--timeout | Timeout when watching (for example, 30m, 1h); implies --watch |
--dry-run | Preview without starting |
--json | Output as JSON |
-w, --web | Open run in browser |
Running remote builds
Trigger a new build by specifying a job ID:
teamcity run start MyProject_Build
Add --watch to follow the build in real time:
teamcity run start MyProject_Build --branch main --watch
The --watch flag displays a live progress view that updates until the build completes.

View logs and troubleshooting builds
View the log output from a specific build:
teamcity run log 12345
Or get the latest log for a job:
teamcity run log --job MyProject_Build
Using logs, we can investigate a failed build. When a build fails, use this workflow to quickly find the root cause:
-
Find the failed build:
teamcity run list --status failure -
View failure diagnostics (problems, failed tests with full stack traces):
teamcity run log 12345 --failed -
Inspect individual test failures:
teamcity run tests 12345 --failed
view snapshot dependency tree
Visualize the snapshot dependency chain for a run with teamcity run tree:
teamcity run tree 12345

Here are the flags you have:
| Flag | Description |
|---|---|
-d, --depth | Limit tree depth (0 = unlimited) |
--json | Output as JSON |
running personal builds
The teamcity run start <JobName> command lets you rerun a job in the teamcity cloud, specified by a job name.
The below command lets you run changes you made locally in the repo of the VCS root configured for the specific build configuration (job) you're running.
teamcity run start <JobName> --local-changes --watch
Include uncommitted local changes in a personal build:
# Auto-detect changes from Git working directory
teamcity run start MyProject_Build --local-changes
# From a patch file
teamcity run start MyProject_Build --local-changes changes.patch
# From stdin
git diff | teamcity run start MyProject_Build --local-changes -
By default, the CLI pushes your branch to the remote before starting a personal build. Use --no-push to skip this:
teamcity run start MyProject_Build --local-changes --no-push
dry run
Preview what would be triggered without actually starting a build:
teamcity run start MyProject_Build --dry-run
artifacts
list artifacts
List artifacts from a run without downloading them:
teamcity run artifacts 12345
teamcity run artifacts --job MyProject_Build
teamcity run artifacts 12345 --path html_reports/coverage
teamcity run artifacts 12345 --json
download artifacts
Download artifacts from a completed run:
teamcity run download 12345
teamcity run download 12345 --path build/assets
teamcity run download 12345 -o ./artifacts
teamcity run download 12345 --artifact "*.jar"
teamcity run download 12345 --path build/assets -a "*.js"
teamcity run download 12345 --timeout 30m
The --timeout flag sets the maximum time for the entire download operation (default: 10m). Use longer values for large artifact sets, for example --timeout 1h.
comparing runs
Compare two runs side-by-side and highlight what changed between them — status, duration, agent, parameters, test results, problems, and VCS changes:
teamcity run diff 12345 12346
If only one run ID is given, the CLI compares it against the previous finished run of the same job — handy for "what changed since last time?":
teamcity run diff 12345

diffing from logs
Pass --log to compare the two build logs as a colored unified diff. Timestamps, temp paths, and noisy git progress lines are normalized so the diff focuses on real content:
teamcity run diff 12345 12346 --log
teamcity run diff 12345 12346 --log -U5 # 5 lines of context
The output is piped through your pager ($PAGER, defaults to less). Strip colors and pipe to an external diff viewer for richer rendering:
teamcity run diff 12345 12346 --log --no-color | delta
teamcity run diff 12345 12346 --log --no-color | diff-so-fancy
diffing in browser
Open both runs in the browser:
teamcity run diff 12345 12346 --web
diff output in JSON
Machine-readable output for scripts:
teamcity run diff 12345 12346 --json
comments
Set a comment on a run:
teamcity run comment 12345 "Deployed to production"
View the current comment:
teamcity run comment 12345
Delete the comment:
teamcity run comment 12345 --delete
teamcity job
NOTE
Job IDs like MyProject_Build are not the same as display names. Always use the ID column from the output when trying to query by a specific job.
teamcity job list: browse available jobs, which are just build configurations
# List all jobs
teamcity job list
# Filter by project
teamcity job list --project MyProject

teamcity job view <job>: view detail of a specific job
listing jobs
Filter by project:
teamcity job list --project MyProject
Limit the number of results:
teamcity job list --limit 20
Output as JSON:
teamcity job list --json
teamcity job list --json=id,name,projectName,webUrl

| Flag | Description |
|---|---|
-p, --project | Filter by project ID |
-n, --limit | Maximum number of jobs to display |
--json | Output as JSON. Use --json= to list available fields, --json=f1,f2 for specific fields. |
viewing specific job
View details of a build configuration:
teamcity job view MyProject_Build
Open the job page in your browser:
teamcity job view MyProject_Build --web
Output as JSON:
teamcity job view MyProject_Build --json
snapshot dependency tree
Visualize the snapshot dependency chain for a job. By default, the tree shows both dependents (what gets triggered after this job) and dependencies (what must run before this job):
teamcity job tree MyProject_DeployStaging
Show only one direction:
teamcity job tree MyProject_Build --only dependents
teamcity job tree MyProject_Deploy --only dependencies
Limit the tree depth:
teamcity job tree MyProject_Build --depth 2

| Flag | Description |
|---|---|
-d, --depth | Limit tree depth (0 = unlimited) |
--only | Show only dependents or dependencies |
build configuration parameters
Read parameters
View all parameters defined on a job:
teamcity job param list MyProject_Build
teamcity job param list MyProject_Build --json
Retrieve the value of a specific parameter:
teamcity job param get MyProject_Build VERSION
teamcity job param get MyProject_Build env.JAVA_HOME
Set parameters
Set or update a parameter value:
teamcity job param set MyProject_Build VERSION "2.0.0"
To mark a parameter as secure (password field), use the --secure flag. Secure parameters have their values hidden in the web interface and build logs:
teamcity job param set MyProject_Build SECRET_KEY "my-secret-value" --secure
Delete parameter
Remove a parameter from a job:
teamcity job param delete MyProject_Build MY_PARAM
teamcity project
teamcity project list: list projects
teamcity project list --limit 20
teamcity project list --json
teamcity project list --json=id,name,parentProjectId,webUrl
teamcity project view <project-name>: view detailed info of a specific projectteamcity project tree <project-name>: view project hierarchy
listing projects
| Flag | Description |
|---|---|
-p, --parent | Filter by parent project ID |
-n, --limit | Maximum number of projects to display |
--json | Output as JSON. Use --json= to list available fields, --json=f1,f2 for specific fields. |
view project details
View details of a project:
teamcity project view MyProject
Open the project page in your browser:
teamcity project view MyProject --web
Output as JSON:
teamcity project view MyProject --json
view project hierarchy
Display the project hierarchy as a tree, including subprojects and build configurations using the teamcity project tree command.
Build default, this shows the hierarchy of the root project
teamcity project tree

Show a specific subtree:
teamcity project tree MyProject
Hide build configurations to see only the project structure:
teamcity project tree --no-jobs
Limit the tree depth:
teamcity project tree --depth 2
| Flag | Description |
|---|---|
--no-jobs | Hide build configurations, show only projects |
-d, --depth | Limit tree depth (0 = unlimited) |
ssh keys
SSH keys uploaded to a project can be used for VCS root authentication (TEAMCITY_SSH_KEY auth method). Keys are inherited by child projects.
Listing SSH keys
teamcity project ssh list --project MyProject
teamcity project ssh list --project MyProject --json
Generating an SSH key pair
Generate an ed25519 or RSA key pair directly in TeamCity and print the public key:
teamcity project ssh generate --name deploy-key --project MyProject
teamcity project ssh generate --name deploy-key --type rsa --project MyProject
Add the printed public key as a deploy key in your Git hosting provider
Uploading an SSH key
teamcity project ssh upload ~/.ssh/id_ed25519 --project MyProject
teamcity project ssh upload key.pem --name my-deploy-key --project MyProject
Deleting an SSH key
teamcity project ssh delete my-deploy-key --project MyProject
teamcity project ssh delete my-deploy-key --project MyProject --yes
validating versioned settings
Check the synchronization status of versioned settings for a project:
teamcity project settings status MyProject
teamcity project settings status MyProject --json
This displays whether versioned settings are enabled, the current sync state, last successful sync timestamp, VCS root and format information, and any errors from the last sync attempt.
You can locally validate if your Kotlin DSL in a .teamcity folder is correct:
Validate Kotlin DSL configuration by running the TeamCity configuration generator:
teamcity project settings validate
teamcity project settings validate ./path/to/.teamcity
teamcity project settings validate --verbose
The command auto-detects the .teamcity directory in the current directory or its parents. It requires Maven (mvn) or uses the Maven wrapper (mvnw) if present in the DSL directory.
teamcity agent
The teamcity agent commands family lets you manage agents, SSH into them, and more.
teamcity agent list: lists all agents associated with your Teamcity serverteamcity agent term <agent-id>: SSH into a specific agentteamcity agent exec <agent-id> <cmd>: Execute a command within the context of an agent's terminal
Validate Kotlin DSL configuration by running the TeamCity configuration generator:
teamcity project settings validate
teamcity project settings validate ./path/to/.teamcity
teamcity project settings validate --verbose
The command auto-detects the .teamcity directory in the current directory or its parents. It requires Maven (mvn) or uses the Maven wrapper (mvnw) if present in the DSL directory
teamcity config
TeamCity CLI stores its configuration in a YAML file at ~/.config/tc/config.yml. This file is created automatically when you run teamcity auth login.
A typical configuration file looks like this and contains these components:
- server list: a list of teamcity servers the CLI can connect to, and a setting for the default server to connect to. You also have these additional settings on each server:
- default user: specified by
user,the default teamcity user to authenticate with when using that server. - readonly: specified by
ro, where if set totrue, then readonly access to that server is granted.
- default user: specified by
- aliases: a list of additional aliased commands to use with the
teamcityCLI.
default_server: https://teamcity.example.com
servers:
https://teamcity.example.com:
user: alice
https://teamcity-staging.example.com:
user: alice
guest: true
https://teamcity-prod.example.com:
user: alice
ro: true
aliases:
rl: 'run list'
rw: 'run view $1 --web'
mine: 'run list --user=@me'
The config command lets you view and modify CLI settings without editing the YAML file directly.
Here are the available keys you can set for the configuration settings.
| Key | Scope | Description |
|---|---|---|
default_server | Global | The default TeamCity server URL. |
guest | Per-server | Enable guest authentication (no token needed). Use --server to target a specific server. |
ro | Per-server | Enable read-only mode (blocks all write operations). Use --server to target a specific server. |
token_expiry | Per-server | Token expiry timestamp (RFC 3339). Normally set by auth login. |
analytics | Global | Enable or disable anonymous usage statistics. Default: true. Set to false to opt out. |
NOTE
Authentication fields (token, user) are managed by teamcity auth login/teamcity auth logout and cannot be set via config set.
You have complete CRUD over settings.
- list all settings
teamcity config list
teamcity config list --json
- get a specific setting
teamcity config get default_server
teamcity config get ro --server tc.example.com
- set a setting
# Switch default server
teamcity config set default_server tc.example.com
# Enable read-only mode for a specific server
teamcity config set ro true --server tc.example.com
# Enable guest auth for the default server
teamcity config set guest true
Aliases
The teamcity alias command lets you have complete CRUD over the aliases in the teamcity config file, which are stored in the aliases section of ~/.config/tc/config.yml:
View all configured aliases:
teamcity alias list
teamcity alias list --json
Remove an alias:
teamcity alias delete rl
creating aliases
Create an alias with teamcity alias set
teamcity alias set rl 'run list'

positional arguments
Use $1, $2, and so on for positional arguments:
teamcity alias set rw 'run view $1 --web'
- Now
teamcity rw 12345expands toteamcity run view 12345 --web.
NOTE
Extra arguments that do not match a placeholder are appended to the end of the expanded command.
shell aliases
For aliases that need pipes, redirection, or other shell features, prefix the expansion with ! or use the --shell flag:
teamcity alias set watchnotify '!teamcity run watch $1 && notify-send "Build $1 done"'
teamcity alias set faillog '!teamcity run list --status=failure --json | jq ".[].id"'
Shell aliases are evaluated through sh instead of being expanded directly.
useful aliases
teamcity alias set rl 'run list'
teamcity alias set rv 'run view $1'
teamcity alias set rw 'run view $1 --web'
teamcity alias set jl 'job list'
teamcity alias set ql 'queue list'
teamcity alias set mine 'run list --user=@me'
teamcity alias set fails 'run list --status=failure --since=24h'
teamcity alias set running 'run list --status=running'
teamcity alias set morning 'run list --status=failure --since=12h'
teamcity alias set go 'run start $1 --watch'
teamcity alias set try 'run start $1 --local-changes --watch'
teamcity alias set hotfix 'run start $1 --top --clean --watch'
teamcity alias set retry 'run restart $1 --watch'
teamcity alias set rush 'queue top $1'
teamcity alias set ok 'queue approve $1'
teamcity alias set whoami "api '/app/rest/users/current'"
teamcity alias set watchnotify '!teamcity run watch $1 && notify-send "Build $1 done"'
teamcity alias set faillog '!teamcity run list --status=failure --json | jq ".[].id"'
Reference
Run teamcity <command> --help for usage
| Group | Commands |
|---|---|
| auth | login, logout, status |
| run | list, start, view, watch, log, tree, changes, tests, diff, cancel, download, artifacts, restart, pin/unpin, tag/untag, comment |
| job | list, view, create, tree, pause/resume, step list/view/add/delete, param list/get/set/delete, settings list/get/set |
| project | list, view, create, tree, vcs list/view/create/test/delete, ssh list/generate/upload/delete, cloud profile/image/instance, connection list/view/create github-app/create docker/authorize/delete, param, token get/put, settings export/status/validate |
| pipeline | list, view, create, validate, pull, push, schema, delete |
| queue | list, approve, remove, top |
| agent | list, view, term, exec, jobs, authorize/deauthorize, enable/disable, move, reboot |
| pool | list, view, link/unlink |
| server | plugin upload (optionally with --hot-reload) |
| api | Raw REST API access |
| link | Bind this repository to a TeamCity project via teamcity.toml |
| config | list, get, set |
| alias | set, list, delete |
| skill | list, install, remove, update |
| update | Check for CLI updates |
Completion
bash
teamcity completion bash > ~/.teamcity-completion.bash
echo 'source ~/.teamcity-completion.bash' >> ~/.bashrc
Powershell
teamcity completion powershell > teamcity.ps1
. ./teamcity.ps1
AI
The CLI ships with an Agent Skill that teaches coding agents (Claude Code, Cursor, and others) how to drive teamcity:
teamcity skill install # auto-detects installed agents
teamcity skill install --project # install to current project only
teamcity skill update # update to the version bundled with teamcity
teamcity skill remove # uninstall
or specifically for Claude Code:
/plugin marketplace add JetBrains/teamcity-cli
/plugin install teamcity-cli@teamcity-cli
Teamcity DSL
If you want to write config as code much like how YAML files are used to create github action workflows, you can do the same thing as Teamcity with XML files that represent build configurations that live in the .teamcity folder within a repo, to have automatic gitOps configuration with teamcity when pushing up your repo.
Kotlin DSL for Teamcity compile into these XML files behind the scenes, so that's what we'll use for our config as code.
Teamcity to gitlab necessary setup
For more info, go here:
Creating project with .teamcity config
If you want to create a project from scratch using a repo that has a .teamcity folder in it, this is what you should follow:
It is possible to import existing Kotlin settings. When creating the project from a repository URL, TeamCity will scan the sources. If existing Kotlin settings are detected, the wizard will suggest importing them.

Here are the full steps:
- Configure a git repo for source control:

- For the version settings of a project make sure to enable these settings.
- synchronization enabled: use project settings from VCS root
- VCS root: specify which repo and which branch to look inside for the
.teamcityfolder of configuration files. - build start settings: use the VCS as the source of truth for providing teamcity build configuration settings.
- settings format: Choose Kotlin to use the Kotlin DSL.

- Make sure you have a teamcity user on your gitlab repo that has READ/WRITE access to the repo
Teamcity Kotlin DSL syntax primer
TeamCity calls a build configuration a BuildType in its Kotlin DSL API. The API groups its settings into blocks such as vcs, steps, triggers, failureConditions, and features.
TeamCity UI Kotlin DSL
Project Project
Build Configuration BuildType
VCS Root VcsRoot / GitVcsRoot
Build Step BuildStep
Build Template Template
Basically, each object in TeamCity (project, build step, build configuration, etc.) corresponds to a class in the Kotlin DSL
Lambda receiver syntax
Consider:
project {
buildType {
name = "Build Application"
steps {
script {
scriptContent = "./gradlew build"
}
}
}
}
Do not read this as magic syntax.
Read it as:
configure project
↓
create/configure a build type
↓
configure its build steps
↓
create/configure a script step
↓
set its scriptContent property
The simplest Kotlin DSL setup
import jetbrains.buildServer.configs.kotlin.*
import jetbrains.buildServer.configs.kotlin.buildSteps.script
version = "YOUR_TEAMCITY_VERSION"
project {
buildType {
id("BuildApplication")
name = "Build Application"
steps {
script {
name = "Compile application"
scriptContent = "./gradlew build"
}
}
}
}
- Create a
Projectinstance with theproject { }block, which declares a project in Teamcity. - Create a
BuildTypeinstance with thebuildType { }block, which declares a build configuration within the project.- The
id("BuildApplication")creates a stable ID for the build configuration, which is what you use to uniquely identify the build configuration across all TeamCity projects. - The
steps {}block creates a list of build steps for the job
- The
id vs name
This distinction matters a lot in real projects.
id: used for creating a logical ID for the build configuration or templatename: used for creating a human-readable facing name for the build configuration or template.
You might have:
id("BuildBackend")
name = "Build Backend"
Later someone wants prettier wording:
name = "Compile Backend"
The identity should usually remain the same, because you should think of it as an unchanging logical ID
id("BuildBackend")
.teamcity folder structure
After enabling Versioned Settings and configuring the synchronization, TeamCity automatically creates a new .teamcity folder within your repository. This folder contains the following two initial required files:
pom.xml: This file defines the folder as a Maven project, which is necessary for IntelliJ IDEA to properly recognize and provide features like auto-completion for your configuration scripts.settings.kts: This is your primary Kotlin script file where the project's build configuration logic is stored.
my-application/
├── src/
├── build.gradle.kts
│
└── .teamcity/
├── settings.kts
└── pom.xml
settings.kts
The settings.kts is the entrypoint for describing the Teamcity build configuration. A standard Teamcity build configuration will have many objects that compose a pipeline.
A standard, simple configuration file includes several key blocks:
- Version definition: At the top, the file specifies the TeamCity server version that generated the script, ensuring compatibility, using the
versionvariable
version = "2022.04"
- Project block: The
projectblock defines the scope of your TeamCity project, and you must define several sub blocks and objects here:buildType(buildConfiguration: BuildType): defines a build configuration job that runs multiple steps, associating aBuildTypeobject with the project.
project {
subProject {
id("CustomerPortalBuilds")
name = "Customer Portal Builds"
buildType(APIBuild)
buildType(ReactBuild)
}
vcsRoot(PortalAppVcsRoot)
vcsRoot(PortalApiVcsRoot)
buildType(Publish)
}
-
Build types: Within the project, you define build types (equivalent to build configurations in the UI) by instantiating
BuildTypeobjects that TeamCity DSL recognizes. Here are the different things you can set on an individual build configuration:- VCS Roots (
vcs): Thevcsblock links your project to the repository.- The reference
DslContext.settingsRootensures the project uses the same repository as the configuration file itself
- The reference
object Build: BuildType({name = "build"id("build")vcs {root(DslContext.settingsRoot)}}- Build steps (
steps): This defines the actual work, such as a Maven step with specific goals likeclean testto run project tests. It can also include runner arguments to control behavior, such as ensuring all tests execute
object Build: BuildType({name = "build"id("build")vcs {root(DslContext.settingsRoot)}steps {exec {name = "NPM Install"workingDir = ""path = "npm"arguments = "install"}exec {name = "NPM Build"workingDir = ""path = "npm"arguments = "run build"}}- Build features: configuration for the build agent execution runner environment and also third-party stuff like running builds on pull requests, adding minimum CPU and compute constraints, and more.
object Build: BuildType({name = "build"id("build")vcs {root(DslContext.settingsRoot)}// add featuresfeatures {// define build agent VM constraintfreeDiskSpace {requiredSpace = "6gb"failBuild = true // fail build if not satisfied}}- Triggers: A
triggerssection handles automation. An emptyvcstrigger configuration defaults to polling the repository for changes every 60 seconds
object Build: BuildType({name = "build"id("build")vcs {root(DslContext.settingsRoot)}// add triggerstriggers {// confgure VCS trigger that creates trigger from VCS rootvcs {quietPeriodMode = VcsTrigger.QuietPeriodMode.USE_CUSTOMquietPeriod = 300branchFilter = ""}}}- dependencies: You can create build chains which are the equivalent of job dependencies in github actions to create sequential builds that depend on each other.
- VCS Roots (
Here's a basic, simple example that does the following:
version = "2020.1"
// project configuration lives here
project {
// 1. Use the build configuration specified by the Build object we created
buildType(Build)
}
// 2. create a build configuration with the BuildType class
object Build: BuildType({
name = "build"
id("build")
vcs {
root(DslContext.settingsRoot)
}
steps {
exec {
name = "NPM Install"
workingDir = ""
path = "npm"
arguments = "install"
}
exec {
name = "NPM Build"
workingDir = ""
path = "npm"
arguments = "run build"
}
}
triggers {
vcs {
quietPeriodMode = VcsTrigger.QuietPeriodMode.USE_CUSTOM
quietPeriod = 300
branchFilter = ""
}
}
})
pom.xml
The pom.xml reads settings from the settings.kts to define the build configuration
<?xml version="1.0"?>
<project>
<modelVersion>4.0.0</modelVersion>
<name>Corporate_CustomerPortal Config DSL Script</name>
<groupId>Corporate_CustomerPortal</groupId>
<artifactId>Corporate_CustomerPortal_dsl</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.jetbrains.teamcity</groupId>
<artifactId>configs-dsl-kotlin-parent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<repositories>
<repository>
<id>jetbrains-all</id>
<url>https://download.jetbrains.com/teamcity-repository</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>teamcity-server</id>
<url>http://ci.compusearch.com/app/dsl-plugins-repository</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>JetBrains</id>
<url>https://download.jetbrains.com/teamcity-repository</url>
</pluginRepository>
</pluginRepositories>
<build>
<sourceDirectory>${basedir}</sourceDirectory>
<plugins>
<plugin>
<artifactId>kotlin-maven-plugin</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
<version>${kotlin.version}</version>
<configuration/>
<executions>
<execution>
<id>compile</id>
<phase>process-sources</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>process-test-sources</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jetbrains.teamcity</groupId>
<artifactId>teamcity-configs-maven-plugin</artifactId>
<version>${teamcity.dsl.version}</version>
<configuration>
<format>kotlin</format>
<dstDir>target/generated-configs</dstDir>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.jetbrains.teamcity</groupId>
<artifactId>configs-dsl-kotlin</artifactId>
<version>${teamcity.dsl.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.teamcity</groupId>
<artifactId>configs-dsl-kotlin-plugins</artifactId>
<version>1.0-SNAPSHOT</version>
<type>pom</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<version>${kotlin.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-script-runtime</artifactId>
<version>${kotlin.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
Advanced structure
Teamcity requires you to split your settings.kts once it reaches over 20 entities (build configurations, templates, VCS roots, etc.)
Here is how you should split them:
.teamcity
└─── pom.xml
└─── settings.kts # Stores only the Kotlin DSL version and the "project(_Self.Project)" line
└─── _Self
└─── ...
└─── ...
└─── ProjectA
└─── ...
└ ─── ...
└─── ProjectA_Subproject1
└─── ...
└─── ...
└─── ProjectA_Subproject2
└─── ...
└─── ...
└─── ProjectB
└─── ...
└─── ...
└───...
└───...
Each folder (including the "_Self" folder for the <Root> project) has the following structure:
ProjectA
└─── Project.kt # Stores a list of subprojects, parameters, connections, and other project-level settings
└─── buildTypes # A folder with .kt files that define build configurations, their steps, triggers, build features, and more
└─── ProjectA_MyBuildConfig1.kt
└─── ProjectA_MyBuildConfig2.kt
└─── vcsRoots # A folder with .kt files that define VCS roots (for example, custom GitVcsRoot class descendants)
└─── ProjectA_MyRoot1.kt
└─── ProjectA_MyRoot2.kt
Creating a project
The settings.kts should contain a single invocation of the project block, which is where all the build configuration, VCS settings, and subproject settings are configured.
VcsRoot, BuildType, Template, and subprojects objects can be registered inside the project using the vcsRoot(), buildType(), template(), and subProject() methods respectively:
project {
subProject {
id("CustomerPortalBuilds")
name = "Customer Portal Builds"
buildType(APIBuild)
buildType(ReactBuild)
}
vcsRoot(PortalAppVcsRoot)
vcsRoot(PortalApiVcsRoot)
buildType(Publish)
}
Suppose we have:
object BuildApplication : BuildType({
name = "Build Application"
})
You've created a Kotlin object representing a build configuration.
But the project still needs to include it:
project {
buildType(BuildApplication)
}
Think:
Define object:
BuildApplication
↓
Register object with project:
project {
buildType(BuildApplication)
}
The same principle applies to the VCS root and any other project-level teamcity object like a build configuration, template, subproject, or VCS root.
project {
vcsRoot(ApplicationRepository)
}
Here's a list of what you have to explicitly register on a project in order for it to be included in the project settings:
- build configurations: register
BuildTypebuild configuration instances with thebuildType()method, which takes in aBuildTypeobject instance. - templates: register
Templatebuild configuration template with thetemplate()method, which takes in aTemplateobject instance. - subprojects: register a
SubProjectwith thesubProject()method, which takes in aSubProjectobject instance, registering a subproject within the project. - vcs roots: register a VCS root represented by the
GitVcsRootclass with thevcsRoot()method, which takes in aGitVcsRootobject instance. - build chains: the
sequential { }andparallel { }blocks allow you to create build chains and then register build configurations within those build chains with thebuildType()method, which takes in aBuildTypeobject instance.
VCS roots
In the projects block, you can register VCS roots for the project via the vcsRoot() function, which takes in a GitVcsRoot instance:
project {
vcsRoot(PortalAppVcsRoot)
vcsRoot(PortalApiVcsRoot)
}
object PortalAppVcsRoot : GitVcsRoot({
name = "Portal App VCS Root"
url = "git@gitlab.compusearch.com:corporatecomponents/customerportal/portalapp.git"
branch = "main"
authMethod = uploadedKey {
uploadedKey = "tc_gitlab.id_rsa"
}
})
object PortalApiVcsRoot : GitVcsRoot({
name = "Portal API VCS Root"
url = "git@gitlab.compusearch.com:corporatecomponents/customerportal/portalapi.git"
branch = "main"
authMethod = uploadedKey {
uploadedKey = "tc_gitlab.id_rsa"
}
})
Here are the basic properties that the GitVcsRoot object takes in:
name: the name to set for the VCS rooturl: either the HTTPS or SSH url of the repo to connect tobranch: the git branch to use as the source, like"main"

auth methods
There are two ways to authenticate with a Git repo when setting up the Git VCS root:
- Method 1 - HTTPS: for the
urlproperty you pass the HTTPS URL to your github repo, and then for theauthMethod, you specify HTTPS - Method 2 - SSH: for the
urlproperty you pass the SSH URL to connect to your github repo in<user>@<host>style, and then use theuploadedKeylambda to specify the public key in gitlab that should be used to connect to the private key in Teamcity (check out [[#Teamcity + Gitlab SSH keys]] for more info). - method 3 - refresheable access token: use refreshable access tokens to authenticate and let TeamCity pull down the repo without an explicit SSH key. To enable this option, you must create a [[#VCS connection]] first.
object MyRepo : GitVcsRoot({
name = "My Repo"
url = "https://github.com/company-org/api-service.git"
authMethod = token {
//this ID points to a TeamCity refreshable token stored on the server.
tokenId = "PROJECT_EXT_15"
}
})
Branches and feature branches
TeamCity's Git VCS root has separate branch and branchSpec properties.
branch: the default branch to pull from for the VCS root source code.- TeamCity uses the default branch in many places where no other branch has been explicitly chosen.
branchSpec: tell TeamCity which additional feature branches should participate in branch-aware builds, using branch syntax.
object ApplicationRepository : GitVcsRoot({
id("ApplicationRepository")
name = "Application Repository"
url = "https://github.com/example/my-app.git"
branch = "refs/heads/main"
branchSpec = """
+:refs/heads/feature/*
+:refs/heads/release/*
""".trimIndent()
})
Build configuration basics
A build configuration consists of these basic components:

subprojects
complete example
import jetbrains.buildServer.configs.kotlin.v2019_2.*
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.*
import jetbrains.buildServer.configs.kotlin.v2019_2.vcs.GitVcsRoot
import jetbrains.buildServer.configs.kotlin.v2019_2.triggers.VcsTrigger
import jetbrains.buildServer.configs.kotlin.v2019_2.triggers.vcs
/*
The settings script is an entry point for defining a TeamCity
project hierarchy. The script should contain a single call to the
project() function with a Project instance or an init function as
an argument.
VcsRoots, BuildTypes, Templates, and subprojects can be
registered inside the project using the vcsRoot(), buildType(),
template(), and subProject() methods respectively.
To debug settings scripts in command-line, run the
mvnDebug org.jetbrains.teamcity:teamcity-configs-maven-plugin:generate
command and attach your debugger to the port 8000.
To debug in IntelliJ Idea, open the 'Maven Projects' tool window (View
-> Tool Windows -> Maven Projects), find the generate task node
(Plugins -> teamcity-configs -> teamcity-configs:generate), the
'Debug' option is available in the context menu for the task.
*/
version = "2022.04"
project {
subProject {
id("CustomerPortalBuilds")
name = "Customer Portal Builds"
buildType(APIBuild)
buildType(ReactBuild)
}
vcsRoot(PortalAppVcsRoot)
vcsRoot(PortalApiVcsRoot)
buildType(Publish)
}
object PortalAppVcsRoot : GitVcsRoot({
name = "Portal App VCS Root"
url = "git@gitlab.compusearch.com:corporatecomponents/customerportal/portalapp.git"
branch = "main"
authMethod = uploadedKey {
uploadedKey = "tc_gitlab.id_rsa"
}
})
object PortalApiVcsRoot : GitVcsRoot({
name = "Portal API VCS Root"
url = "git@gitlab.compusearch.com:corporatecomponents/customerportal/portalapi.git"
branch = "main"
authMethod = uploadedKey {
uploadedKey = "tc_gitlab.id_rsa"
}
})
object Publish : BuildType({
name = "Publish"
buildNumberPattern = "2026.4.0.%build.counter%"
publishArtifacts = PublishMode.SUCCESSFUL
steps {
step {
name = "Publish Package to Server"
type = "octopus.push.package"
param("octopus_space_name", "%allprojects.octopus.spacename.prodops%")
param("octopus_host", "%allprojects.octopus.url%")
param("octopus_packagepaths", """
aggregateBuilds/** => COCO.CustomerPortal.%build.number%.zip
database/sqlserver/** => COCO.CustomerPortalApi.SqlServerDB.%build.number%.zip
database/oracle/** => COCO.CustomerPortalApi.OracleDB.%build.number%.zip
""".trimIndent())
param("octopus_forcepush", "false")
param("octopus_publishartifacts", "true")
param("secure:octopus_apikey", "credentialsJSON:385844c1-18e7-4a4d-b8eb-1b23541a94ef")
}
step {
name = "Create Release"
type = "octopus.create.release"
param("octopus_space_name", "%allprojects.octopus.spacename.prodops%")
param("octopus_channel_name", "%coco.octopus.channel.unified%")
param("octopus_version", "3.0+")
param("octopus_host", "%allprojects.octopus.url%")
param("octopus_project_name", "Customer Portal - IIS")
param("octopus_forcepush", "IgnoreIfExists")
param("secure:octopus_apikey", "credentialsJSON:385844c1-18e7-4a4d-b8eb-1b23541a94ef")
param("octopus_releasenumber", "%build.number%%coco.octopus.unified.prerelease%")
}
}
params {
param("param.rjs.package", "COCO.CustomerPortalApp.${ReactBuild.depParamRefs.buildNumber}.zip")
param("param.net.package", "COCO.CustomerPortalApi.${APIBuild.depParamRefs.buildNumber}.zip")
param("param.dbsql.package", "COCO.CustomerPortalApi.SqlServerDB.${APIBuild.depParamRefs.buildNumber}.zip")
param("param.dbora.package", "COCO.CustomerPortalApi.OracleDB.${APIBuild.depParamRefs.buildNumber}.zip")
param("coco.octopus.channel.unified", "Unified")
param("coco.octopus.unified.prerelease", "%allprojects.octopus.prereleasetag%")
}
triggers {
vcs {
branchFilter = ""
watchChangesInDependencies = true
}
}
dependencies {
dependency(APIBuild) {
snapshot {
onDependencyFailure = FailureAction.FAIL_TO_START
}
artifacts {
cleanDestination = true
artifactRules = """
%param.net.package%!** => aggregateBuilds
%param.dbsql.package%!** => database/sqlserver
%param.dbora.package%!** => database/oracle
""".trimIndent()
}
}
dependency(ReactBuild) {
snapshot {
onDependencyFailure = FailureAction.FAIL_TO_START
}
artifacts {
cleanDestination = true
artifactRules = "%param.rjs.package%!** => aggregateBuilds/build"
}
}
}
})
object APIBuild : BuildType({
id("APIBuild")
name = "API Build"
artifactRules = """
CustomerPortalApi\bin\net10.0\publish\** => COCO.CustomerPortalApi.%build.number%.zip
-: CustomerPortalApi\bin\net10.0\publish\Migrations => COCO.CustomerPortalApi.%build.number%.zip
CustomerPortalApi\bin\net10.0\publish\Migrations\SqlServer\** => COCO.CustomerPortalApi.SqlServerDB.%build.number%.zip
CustomerPortalApi\bin\net10.0\publish\Migrations\Oracle\** => COCO.CustomerPortalApi.OracleDB.%build.number%.zip
""".trimIndent()
buildNumberPattern = "2026.4.0.%build.counter%"
publishArtifacts = PublishMode.SUCCESSFUL
params {
param("param.solution", "CustomerPortalApi.sln")
param("system.DeployOnBuild", "true")
param("system.PublishProfile", "FolderProfile")
}
vcs {
root(PortalApiVcsRoot, "+:. => .", "-: .teamcity", "-: .idea", "-: .gitignore")
}
steps {
powerShell {
name = "Pull SlowCheetah"
platform = PowerShellStep.Platform.x64
scriptMode = script {
content = """
%teamcity.tool.NuGet.CommandLine.DEFAULT%\tools\nuget install SlowCheetah -OutputDirectory packages -Source "C:\Program Files (x86)\Microsoft SDKs\NuGetPackages;https://api.nuget.org/v3/index.json"
""".trimIndent()
}
}
powerShell {
name = "Run Project Transform Version Update"
platform = PowerShellStep.Platform.x64
scriptMode = file {
path = "%devops.teamcity.tools.dir%/General/Invoke-MsBuildXmlTransform.ps1"
}
param("jetbrains_powershell_scriptArguments", "-NuGetRootPath %teamcity.build.workingDir%/packages -BaseFile %teamcity.build.workingDir%/CustomerPortalApi/log4net.config -TransformFile %teamcity.build.workingDir%/CustomerPortalApi/log4net.Release.config -TargetPath %teamcity.build.workingDir%/CustomerPortalApi/log4net.config")
}
dotnetRestore {
name = "Restore Packages"
projects = "%param.solution%"
sources = """
https://nuget.compusearch.com/v3/index.json
https://api.nuget.org/v3/index.json
""".trimIndent()
param("dotNetCoverage.dotCover.home.path", "%teamcity.tool.JetBrains.dotCover.CommandLineTools.DEFAULT%")
}
dotnetMsBuild {
name = "Run Build"
projects = "%param.solution%"
version = DotnetMsBuildStep.MSBuildVersion.CrossPlatform
targets = "Rebuild"
configuration = "Release"
param("dotNetCoverage.dotCover.home.path", "%teamcity.tool.JetBrains.dotCover.CommandLineTools.DEFAULT%")
}
dotnetPublish {
name = "DotNet Publish"
projects = "%param.solution%"
configuration = "Release"
skipBuild = true
args = "/p:PublishProfile=%system.PublishProfile%"
param("dotNetCoverage.dotCover.home.path", "%teamcity.tool.JetBrains.dotCover.CommandLineTools.DEFAULT%")
}
reSharperInspections {
name = "Run Inspections"
enabled = false
solutionPath = "%param.solution%"
cltPath = "%teamcity.tool.jetbrains.resharper-clt.DEFAULT%"
cltPlatform = ReSharperInspections.Platform.X64
customCmdArgs = "-s=WARNING"
}
dotnetVsTest {
name = "Run Tests"
assemblies = """
Unison.Corporate.CustomerPortal.Tests\bin\Release\net10.0\Unison.Corporate.CustomerPortal.Tests.dll
""".trimIndent()
version = DotnetVsTestStep.VSTestVersion.V15
filter = testCaseFilter {
filter = "TestCategory!=Integration"
}
platform = DotnetVsTestStep.Platform.Auto
coverage = dotcover {
toolPath = "%teamcity.tool.JetBrains.dotCover.CommandLineTools.DEFAULT%"
assemblyFilters = "-:Unison.Corporate.*.Tests"
}
}
}
requirements {
doesNotEqual("system.agent.name", "LXCI01")
}
triggers {
vcs {
quietPeriodMode = VcsTrigger.QuietPeriodMode.USE_CUSTOM
quietPeriod = 300
branchFilter = ""
}
}
})
object ReactBuild : BuildType({
id("ReactBuild")
name = "React Build"
artifactRules = """build\** => COCO.CustomerPortalApp.%build.number%.zip"""
buildNumberPattern = "2026.4.0.%build.counter%"
publishArtifacts = PublishMode.SUCCESSFUL
vcs {
root(PortalAppVcsRoot, "+:. => .", "-: .teamcity", "-: .idea", "-: .gitignore")
}
steps {
exec {
name = "NPM Install"
workingDir = ""
path = "npm"
arguments = "install"
}
exec {
name = "NPM Build"
workingDir = ""
path = "npm"
arguments = "run build"
}
}
triggers {
vcs {
quietPeriodMode = VcsTrigger.QuietPeriodMode.USE_CUSTOM
quietPeriod = 300
branchFilter = ""
}
}
})
Build types in depth
Build type and ID
The simplest build configuration doesn't need a VCS root, triggers, or steps. All it needs is a name and logical ID.
You have two ways of setting this:
- Method 1 - set name: if you set the
nameproperty on aBuildTypeobject, the id will be auto-generated from that name.- For example, in this case the usage of the
id()function call is optional because TeamCity will generate the id based on the class name (HelloWorldin our case).
- For example, in this case the usage of the
object HelloWorld: BuildType({
name = "Hello world"
steps {
script {
scriptContent = "echo 'Hello world!'"
}
}
})
- Method 2 - set name and id
project {
buildType {
id("HelloWorld")
name = "Hello world"
steps {
script {
scriptContent = "echo 'Hello world!'"
}
}
}
}
VCS roots with build configurations
Due to one of the rules of teamcity projects (see [[#TeamCity projects]]), build configurations by default inherit settings from the project they are scoped under, such as VCS roots.
However, you can override the VCS roots a build configuration uses with the vcs { } block, and then use the root(GitVcsRoot) method to register a GitVcsRoot instance as the VCS root for that build configuration.
The vcs{} block is used to define the version control settings, including the list of VCS roots and other attributes.

import jetbrains.buildServer.configs.kotlin.*
import jetbrains.buildServer.configs.kotlin.buildSteps.script
import jetbrains.buildServer.configs.kotlin.vcs.GitVcsRoot
version = "YOUR_TEAMCITY_VERSION"
project {
vcsRoot(ApplicationRepository)
buildType(BuildApplication)
}
object ApplicationRepository : GitVcsRoot({
name = "Application Repository"
url = "https://github.com/example/my-application.git"
branch = "refs/heads/main"
})
object BuildApplication : BuildType({
name = "Build Application"
vcs {
root(ApplicationRepository)
}
steps {
script {
name = "Build"
scriptContent = "./gradlew clean build"
}
}
})
Handling branch filters
If your VCS root supports feature branches (has the branchSpec property of branch rules), then you can override which branches the build configuration should run for via the vcs.branchFilter property:
object BuildApplication : BuildType({
name = "Build Application"
vcs {
root(ApplicationRepository)
branchFilter = """
+:*
-:release/*
""".trimIndent()
}
})
Mental model:
VCS root branchSpec
│
│ What branches does this repository
│ definition know about?
▼
main
feature/*
release/*
│
│ Build configuration branchFilter
▼
main
feature/*
Imagine:
Git repository
│
│ branchSpec
▼
Branches TeamCity tracks
│
│ build configuration branchFilter
▼
Branches available to this BuildType
│
│ trigger branchFilter
▼
Branches that automatically start this build
Each stage narrows the previous one.
Checkout rules
VCS checkout rules allow to map repo paths to build agent paths in the filesystem, which is useful only if you want to change how the repo maps to the build agent filesystem when checking out the source code.
NOTE
When is this useful? For composability and reusability, when you only need one folder or file from a VCS root and not everything.
checkout rules answer two closely related questions:
- Which repository paths does my build need?: what do you specifically want to check out?
- Which repository path changes matter to this build?: if you don't checkout certain folders and files, then they don't contribute to the build, and thus any changes to those omitted files and folders should not register as a VCS change.
Suppose the repository is:
repository/
├── backend/
├── frontend/
├── documentation/
└── infrastructure/
A backend build may only care about:
backend/
So doing something like this, where the Kotlin DSL root() function accepts checkout rules when attaching a VCS root to a BuildType:
vcs {
root(
ApplicationRepository,
"+:backend => backend"
)
}
means conceptually:
Git repository Agent
backend/ -----------------------> backend/
frontend/ not checked out
documentation/ not checked out
infrastructure/ not checked out
checkout rules also affect change detection
Suppose your build uses:
vcs {
root(
ApplicationRepository,
"+:backend => backend"
)
}
and someone changes:
documentation/readme.md
TeamCity can treat that commit as irrelevant for this build because it does not match the attached VCS root's checkout rules. JetBrains specifically notes that checkout rules affect both checked-out files and the changes considered relevant to the build.
Triggers
You define triggers on a build type with the BuildType.triggers object block.
VCS triggers
Here's a basic vcs trigger you can set via the triggers.vcs object, where a new build is triggered upon a new recognized commit to the VCS root.
object BuildApplication : BuildType({
name = "Build Application"
vcs {
root(ApplicationRepository)
}
steps {
script {
name = "Build"
scriptContent = "./gradlew clean build"
}
}
triggers {
vcs {
}
}
})
If you want to add branch filters to narrow down which branches the VCS trigger should trigger on, you can do so:
triggers {
vcs {
branchFilter = """
+:*
-:release/*
""".trimIndent()
}
}
Build steps
Script steps
Sometimes script steps you run via shell can be long, so it's better to load scripts from a file in the repo for better composability and avoiding redundancy.
To keep your settings files neat, it is convenient to store lengthy code instructions in separate files. Such auxiliary scripts can be put in the .teamcity directory alongside the settings files. You can refer to them by their relative paths.
For example, this part of settings.kts creates an object with a function readScript which reads the contents of the file it receives on input:
object Util {
fun readScript(path: String): String {
val bufferedReader: BufferedReader = File(path).bufferedReader()
return bufferedReader.use{ it.readText() }.trimIndent()
}
}
In the build step, we call this function, so it reads the scripts\test.sh file located under the .teamcity directory:
object CommandLineRunnerTest : BuildType({
name = "Command Line Runner Test"
steps {
script {
name = "Imported from a file"
scriptContent = Util.readScript("scripts\\test.sh")
}
}
})
Step execution conditions
Refer to original concept in [[#Step execution conditions]]:
In the Kotlin DSL, you can check the teamcity.agent.jvm.os.name parameter to run the current build step only on Windows agents, as follows:
package _Self.buildTypes
import jetbrains.buildServer.configs.kotlin.*
object MyBuildConfig : BuildType({
// ...
steps {
script {
name = "Step 1"
conditions {
contains("teamcity.agent.jvm.os.name", "Windows")
}
// ...
}
}
})
The sample below illustrates how to utilize parameters that report step exit statuses to create a custom condition (step #3 runs only when step #2 fails and step #1 is successful.), see [[#Step status parameters]] for more info.
package _Self.buildTypes
import jetbrains.buildServer.configs.kotlin.*
object MyBuildConf : BuildType({
steps {
python {
id = "Step1"
// ...
}
python {
id = "Step2"
// ...
}
python {
name = "Step3"
conditions {
equals("teamcity.build.step.status.Step1", "success")
equals("teamcity.build.step.status.Step2", "failure")
}
// ...
}
}
})
NOTE
If you declare multiple execution conditions, the build step will be executed only if all of them are satisfied in the current build run.
Resolving from the .teamcity folder
Problem: I want to generate a TeamCity build configuration based on the data in some file residing in the VCS inside the .teamcity directory.
Solution: You can access the location of the .teamcity directory from DSL scripts with help of the DslContext.baseDir property, for example:
val dataFile = File(DslContext.baseDir, "data/setup.xml")
Build outputs and variable interpolation
In kotlin you can obviously use template string interpolation with the ${} syntax, but did you know you can access TeamCity Kotlin DSL variables as well? Here's what you have access to:
- build output variables: when you instantiate a
BuildTypeobject, you're just creating a normal Kotlin object, so of course you can access properties on it. - vcs root variables: Root IDs are used in build parameters that allow you to read root properties, for example
vcsroot.<ProjectName>_<RootName>.branchandvcsroot.<ProjectName>_<RootName>.url
Publishing an artifact
Build configurations have an artifactRules property which you must set in order to publish artifacts for the build configuration.
object Compile : BuildType({
name = "Compile"
artifactRules = "build/libs/*.jar => app"
vcs {
root(ApplicationRepository)
}
steps {
script {
name = "Compile application"
scriptContent = """
./gradlew clean assemble
""".trimIndent()
}
}
})
Conceptually, here's what's happening:
build/libs/*.jar
│
│ publish as artifact
▼
app/
So if the working directory contains:
build/libs/my-app.jar
TeamCity's published artifacts contain something conceptually like:
app/
└── my-app.jar
The important transition is:
Agent filesystem
build/libs/my-app.jar
│
│ artifactRules
▼
TeamCity artifact storage
app/my-app.jar
Once you set artifact rules, you have two use cases:
- accessing artifacts: you can now access artifacts that the build configuration created
- artifact dependencies: any build configuration that depends on another build configuration that outputs artifacts can establish artifact dependencies and use the artifact outputs from the previous build configuration in the chain.
Build chains
You can consider build configurations as jobs/pipelines, and in order to orchestrate sequential and parallel jobs running according to a specific order, we have to create build chains, which describe dependencies of build configurations on other build configurations via snapshot dependencies and artifact dependencies.
There are two ways to create a build chain (configuring sequential dependencies of build configuration files and thus pipelines):
- Method 1 - Use
BuildType.dependencies: Specify which other builds an individual BuildType instance depends on via thedependenciesblock.- Pro: granular
- Con: gets messy and has messy logic
- Method 2 - specify job order in
projectblock: Specify sequential chains of builds in thesequentialblock in theprojectblock, and parallel blocks with theparallelblock.- Pro: super easy and readable
- Con: lower granularity, can't access individual snapshot properties.
IMPORTANT
Then an important thing to understand once you configure a build chain is that the VCS trigger should only be on the LAST build type in the chain.
Setting snapshot and artifact dependencies
This is how you specify a build configuration that is dependent on another build Configuration:
object Compile = BuildType({
// ...
})
object Test : BuildType({
// ...
dependencies {
snapshot(Compile) {
}
}
})
NOTE
What Does snapshot(Compile) Actually Mean?
Many beginners interpret it as: "Run Compile first", but in reality, it's this:
Test and Compile belong to the same build chain and should operate against synchronized source revisions.
Here's a more fleshed out example showcasing snapshot and artifact dependencies:
- snapshot dependencies: necessary to have one build configuration depend on another
- artifact dependencies: only necessary if you want to access the artifact outputs of the previous build configuration in the chain.
IMPORTANT
It's very important to keep in mind that only the last build configuration in a build chain needs a VCS root, as to avoid duplicate triggering.
// 1. create build configuration that outputs artifacts
object Compile : BuildType({
name = "Compile"
artifactRules = "build/libs/*.jar => app"
vcs {
root(ApplicationRepository)
}
steps {
script {
name = "Compile application"
scriptContent = """
./gradlew clean assemble
""".trimIndent()
}
}
})
// 2. create build configuration
// - dependent on Compile via snapshot dependency only
object Test : BuildType({
name = "Test"
vcs {
root(ApplicationRepository)
}
steps {
script {
name = "Run tests"
scriptContent = "./gradlew test"
}
}
dependencies {
snapshot(Compile) {
}
}
})
// 3. create build configuration
// - dependent on Compile via snapshot and artifact dependency
object Package : BuildType({
name = "Package"
artifactRules = "dist/*.jar"
steps {
script {
name = "Create distribution"
scriptContent = """
mkdir -p dist
cp input/*.jar dist/
""".trimIndent()
}
}
dependencies {
snapshot(Test) {
}
artifacts(Compile) {
artifactRules = "app/*.jar => input"
}
}
// VCS trigger only on last one in build chaiun
triggers {
vcs {
}
}
})
Here is how the Package build configuration uses artifact dependencies:
- Read
artifacts(Compile)as "Download artifacts from Compile." - Read this artifact rule as: "Take JAR files underneath Compile's
appartifact directory and put them intoinputfor this build."
artifactRules = "app/*.jar => input"
- Copy
input/my-app.jartodist/my-app.jarin the script steps - Create an artifact rule publishing the JAR files
artifactRules = "dist/*.jar"
The overall pipeline is:
TeamCity artifact storage
Compile
└── app/
└── my-app.jar
│
│ artifact dependency
▼
Package build agent
input/
└── my-app.jar
more in depth example
Here's an example using Method 1:
- Create the two jobs
object APIBuild : BuildType({
id("APIBuild")
name = "API Build"
vcs {
root(PortalApiVcsRoot, "+:. => .", "-: .teamcity", "-: .idea", "-: .gitignore")
}
steps {
powerShell {
name = "Pull SlowCheetah"
platform = PowerShellStep.Platform.x64
scriptMode = script {
content = """
%teamcity.tool.NuGet.CommandLine.DEFAULT%\tools\nuget install SlowCheetah -OutputDirectory packages -Source "C:\Program Files (x86)\Microsoft SDKs\NuGetPackages;https://api.nuget.org/v3/index.json"
""".trimIndent()
}
}
}
triggers {
vcs {
quietPeriodMode = VcsTrigger.QuietPeriodMode.USE_CUSTOM
quietPeriod = 300
branchFilter = ""
}
}
})
object ReactBuild : BuildType({
id("ReactBuild")
name = "React Build"
vcs {
root(PortalAppVcsRoot, "+:. => .", "-: .teamcity", "-: .idea", "-: .gitignore")
}
steps {
exec {
name = "NPM Install"
workingDir = ""
path = "npm"
arguments = "install"
}
exec {
name = "NPM Build"
workingDir = ""
path = "npm"
arguments = "run build"
}
}
triggers {
vcs {
quietPeriodMode = VcsTrigger.QuietPeriodMode.USE_CUSTOM
quietPeriod = 300
branchFilter = ""
}
}
})
- Create a job that is dependent on those two jobs by setting snapshot and artifact dependencies on that job
object Publish : BuildType({
name = "Publish"
buildNumberPattern = "2026.4.0.%build.counter%"
publishArtifacts = PublishMode.SUCCESSFUL
steps {
step {
name = "Publish Package to Server"
type = "octopus.push.package"
param("octopus_space_name", "%allprojects.octopus.spacename.prodops%")
param("octopus_host", "%allprojects.octopus.url%")
param("octopus_packagepaths", """
aggregateBuilds/** => COCO.CustomerPortal.%build.number%.zip
database/sqlserver/** => COCO.CustomerPortalApi.SqlServerDB.%build.number%.zip
database/oracle/** => COCO.CustomerPortalApi.OracleDB.%build.number%.zip
""".trimIndent())
param("octopus_forcepush", "false")
param("octopus_publishartifacts", "true")
param("secure:octopus_apikey", "credentialsJSON:385844c1-18e7-4a4d-b8eb-1b23541a94ef")
}
step {
name = "Create Release"
type = "octopus.create.release"
param("octopus_space_name", "%allprojects.octopus.spacename.prodops%")
param("octopus_channel_name", "%coco.octopus.channel.unified%")
param("octopus_version", "3.0+")
param("octopus_host", "%allprojects.octopus.url%")
param("octopus_project_name", "Customer Portal - IIS")
param("octopus_forcepush", "IgnoreIfExists")
param("secure:octopus_apikey", "credentialsJSON:385844c1-18e7-4a4d-b8eb-1b23541a94ef")
param("octopus_releasenumber", "%build.number%%coco.octopus.unified.prerelease%")
}
}
// dependent on those two builds because it uses outputs from them
params {
param("param.rjs.package", "COCO.CustomerPortalApp.${ReactBuild.depParamRefs.buildNumber}.zip")
param("param.net.package", "COCO.CustomerPortalApi.${APIBuild.depParamRefs.buildNumber}.zip")
param("param.dbsql.package", "COCO.CustomerPortalApi.SqlServerDB.${APIBuild.depParamRefs.buildNumber}.zip")
param("param.dbora.package", "COCO.CustomerPortalApi.OracleDB.${APIBuild.depParamRefs.buildNumber}.zip")
param("coco.octopus.channel.unified", "Unified")
param("coco.octopus.unified.prerelease", "%allprojects.octopus.prereleasetag%")
}
triggers {
vcs {
branchFilter = ""
watchChangesInDependencies = true
}
}
dependencies {
dependency(APIBuild) {
snapshot {
onDependencyFailure = FailureAction.FAIL_TO_START
}
artifacts {
cleanDestination = true
artifactRules = """
%param.net.package%!** => aggregateBuilds
%param.dbsql.package%!** => database/sqlserver
%param.dbora.package%!** => database/oracle
""".trimIndent()
}
}
dependency(ReactBuild) {
snapshot {
onDependencyFailure = FailureAction.FAIL_TO_START
}
artifacts {
cleanDestination = true
artifactRules = "%param.rjs.package%!** => aggregateBuilds/build"
}
}
}
})
parallel and sequential
here's method 2 in action:
projects {
sequential {
// run the build first
buildType(Build)
// run tests in parallel
parallel {
buildType(UnitTest)
buildType(IntegrationTest)
buildType(e2eTest)
buildType(SAST)
buildType(DAST)
}
// deploy last
buildType(Deploy)
}
}
project {
buildType(Compile)
buildType(Test1)
buildType(Test2)
buildType(Package)
buildType(Deploy)
buildType(Extra)
...
sequential {
buildType(Compile)
parallel (options = { onDependencyFailure = FailureAction.CANCEL }) { // non-default snapshot dependency options
dependsOn(Extra) // extra dependency to be defined in all builds in the parallel block
buildType(Test1)
buildType(Test2)
}
buildType(Package)
buildType(Deploy)
}
}
NOTE
If you define a build chain in a pipeline style, ensure there are no explicit snapshot dependencies defined within the referenced build configurations themselves.
Alternatively, you can register all listed builds after the chain declaration with a simplified syntax:
project {
// build chain definition:
val buildChain = sequential {
...
}
// register all build configurations, referenced in the chain, in the current project:
buildChain.buildTypes().forEach { buildType(it) }
}
If you want to refactor using functions and classes as abstractions over creating BuildType instances, here is what you should do, where now you are using trailing lambda syntax and dynamically registering build types:
project {
// 1. define build chain, get all BuildType objects back in Collection
val bts = sequential {
buildType(Maven(name = "Build", goals = "clean compile"))
parallel {
buildType(Maven(name = "Fast Test", goals = "clean test"))
buildType(Maven(name = "Slow Test", goals = "clean test"))
}
buildType(Maven(name = "Package", goals = "clean package"))
}.buildTypes()
// 2. register all BuildType instances
bts.forEach { buildType(it) }
// 3. Set the VCS trigger on the last build in the build chain
bts.last().triggers {
vcs {
}
}
}
class Maven(public var name: String, public var goals: String): BuildType({
name = this.name,
goals = this.goals
})
Agent requirements
object MyBuildConfig : BuildType({
requirements {
exists("DotNetCoreSDK5.0_Path")
startsWith("teamcity.agent.jvm.os.name", "Windows")
}
})
You can also require environment variable parameters to exist on a build agent for it to be selected.
requirements {
exists("env.JDK_17_0")
exists("env.JDK_21_0")
}
Build features
Build features are specified through the features {} block on a build configuration.
Swabra
Let’s extend this build a little bit. TeamCity provides a feature called Build Files Cleaner, also known as Swabra. Swabra makes sure that files left by the previous build are removed before running new builds.

Parameters
You can set teamcity parameters either at the project level or at the build configuration level:
- project level: parameters that live at the project level are inherited by configurations under that project and its child projects.
project {
params {
param("company.registry", "registry.example.com")
param("env.CI", "true")
}
vcsRoot(ApplicationRepository)
buildType(Compile)
buildType(Test)
buildType(Package)
}
- build configuration: parameters that live on a build configuration are unique to that build configuration, and you can even override project parameters by resetting those parameters at the build configuration level.
object Test : BuildType({
name = "Test"
params {
param("gradle.tasks", "test")
}
vcs {
root(ApplicationRepository)
}
steps {
script {
name = "Run tests"
scriptContent = "./gradlew %gradle.tasks%"
}
}
dependencies {
snapshot(Compile) {
}
}
})
The below code block in a build configuration creates teamcity parameters
params {
param("gradle.tasks", "test")
}
Current TeamCity parameters are referenced in most TeamCity settings with %parameterName%.
So:
Kotlin val
│
└── DSL programming concept
TeamCity parameter
│
└── TeamCity runtime/configuration concept
Parameter types
There are four different types of variables/parameters in TeamCity:
- configuration parameter: standard normal parameter you set, which you supply at runtime via the TeamCity UI settings.
- environment variable: sets an environment variable in the build agent running that build configuration, set during the duration of the run.
- kotlin variables: standard kotlin variables you set
- DSL context parameters: project-wide parameters you set on the versioned settings of a project and then you can fetch at runtime via the
DSLContext.getParameter()method.
By this point, TeamCity DSL has several types of “variables.”
- standard kotlin variables
val serviceName = "payments"
- DSL evaluation context
DslContext.getParameter("Region", "us-east")
- TeamCity build/configuration parameter
param("deployment.environment", "staging")
- process environment variable
param("env.JAVA_HOME", "/opt/java")
Here's a full example using all of them:
import jetbrains.buildServer.configs.kotlin.*
import jetbrains.buildServer.configs.kotlin.buildSteps.script
import jetbrains.buildServer.configs.kotlin.triggers.vcs
import jetbrains.buildServer.configs.kotlin.vcs.GitVcsRoot
version = "YOUR_TEAMCITY_VERSION"
project {
params {
param("company.registry", "registry.example.com")
param("env.CI", "true")
}
vcsRoot(ApplicationRepository)
buildType(BuildApplication)
}
object ApplicationRepository : GitVcsRoot({
id("ApplicationRepository")
name = "Application Repository"
url = "git@github.com:company/application.git"
branch = "refs/heads/main"
branchSpec = """
+:refs/heads/feature/*
+:refs/heads/release/*
""".trimIndent()
authMethod = uploadedKey {
uploadedKey = "teamcity-github-key"
}
checkoutPolicy =
GitVcsRoot.AgentCheckoutPolicy.AUTO
})
object BuildApplication : BuildType({
id("BuildApplication")
name = "Build Application"
params {
param("gradle.tasks", "clean build")
}
vcs {
root(ApplicationRepository)
checkoutMode = CheckoutMode.AUTO
}
steps {
script {
name = "Build"
scriptContent = """
echo "CI = ${'$'}CI"
./gradlew %gradle.tasks%
echo "Registry: %company.registry%"
""".trimIndent()
}
}
triggers {
vcs {
branchFilter = """
+:*
-:release/*
""".trimIndent()
}
}
})
Configuration variables
params {
param("deployment.environment", "staging")
}
You can then reference in teamcity:
%deployment.environment%
Environment variables
Declaring environment variables as team city parameters sets the environment variables within the environment for the build agent during the run duration of the build configuration, which means you can access this environment variable within build configuration steps.
- Declare an env var param in the build configuration under the
envobject:
params {
param("env.DEPLOYMENT_ENVIRONMENT", "staging")
}
- You can now access that environment variable in shell code scripts, but make sure to escape the
$
scriptContent = """
echo "Environment: ${'$'}DEPLOYMENT_ENVIRONMENT"
./gradlew %gradle.tasks%
""".trimIndent()
Here's an example
params {
param("gradle.tasks", "test")
param("env.TEST_ENVIRONMENT", "ci")
}
Then
scriptContent = """
echo "Environment: ${'$'}TEST_ENVIRONMENT"
./gradlew %gradle.tasks%
""".trimIndent()
Kotlin DSL evaluation
│
▼
TeamCity configuration
│
│ replaces %...%
▼
shell script
│
│ expands $...
▼
actual command
Secure parameters
In general, TeamCity processes a DSL parameter as a string. To mark a DSL value as secure, you can assign it to a parameter of the password type:
params {
password("<parameter_name>", "credentialsJSON:<token>")
}
<parameter_name>: is a unique name which can be used as a key for accessing the secure value via DSL (for example, in the File Content Replacer build feature)<token>: is the token corresponding to the target secure value, which you generate in the Teamcity UI.
NOTE
The important part is that the committed value is a TeamCity secure-value token, not your plaintext API token. JetBrains explicitly recommends generating a token rather than storing the plaintext password in DSL.
Here's an example:
params {
password("pass-to-bucket", "credentialsJSON:12a3b456-c7de-890f-123g-4hi567890123")
}
secure parameters as environment variables
We can make the secret become an environment variable for the process so the build configuration steps can use that secret, all the while TeamCity handles it as a protected parameter.
- Create a secure parameter that gets stored as an encrypted environment variable on the build agent:
params {
password(
"env.API_TOKEN",
"credentialsJSON:..."
)
}
- Create a build step that uses that secret environment variable
scriptContent = """
curl \
-H "Authorization: Bearer ${'$'}API_TOKEN" \
https://example.com/api
""".trimIndent()
Think of it like:
Git repository
settings.kts
│
└── credentialsJSON:abc...
│
▼
TeamCity secure value
│
▼
Build runtime
Secure parameters via secret stores
NOTE
For higher-security environments, current TeamCity also supports remote-secret parameter types backed by external secret stores like AWS secrets manager.
DSL Context Parameters
You can customize the DSL generation behavior using context parameters configured in the TeamCity UI.
Context parameters are specified as a part of the project versioned settings in the UI.
- With context parameters, it is possible to maintain a single Kotlin DSL code and use it in different projects on the same TeamCity server.
- Each of these projects can have own values of context parameters, and the same DSL code can produce different settings based on values of these parameters.
For more info, check out Kotlin DSL | TeamCity On-Premises And Creating TeamCity project templates with Kotlin DSL context parameters - The JetBrains Blog
Now you can use them in kotlin via the DslContext.getParameter() method, which follows this syntax:
val param = DslContext.getParameter(
parameterName: string,
default: string
)
Here's an example, where if the "Environment" DSL parameter is not populated in versioned settings Teamcity UI settings for the project, it resolves to the default "development" value during runtime.
val environment =
DslContext.getParameter(
"Environment",
"development"
)
object Deploy : BuildType({
name = "Deploy to $environment"
})
Patches
In TeamCity, when your project configuration is stored as Kotlin DSL in version control, the system tries to automatically commit changes made in the web UI back to your code.
TeamCity allows editing a project settings via the web interface, even though the project settings are stored in Kotlin DSL. If DSL scripts were not changed manually, that is if they were generated by TeamCity and stay the same way in the repository, then changes via the web UI will be applied directly to these generated files.
But if generated files were changed, then TeamCity will have to produce a patch, since it no longer knows what part of .kt or .kts file should be changed.
In case of portable DSL the patches are placed under the .teamcity/patches directory, for example:
patches/projects/<relative project id>.kts
patches/buildTypes/<relative build configuration id>.kts
patches/templates/<relative vcs root id>.kts
patches/vcsRoots/<relative build configuration id>.kts
However, DSL patches are created when TeamCity cannot automatically map a UI-driven change to your existing Kotlin code structure.
Here's how they work
- Trigger: Patches occur when you have customized your Kotlin files in a way that TeamCity cannot parse or reconcile automatically, such as by introducing custom variables or complex logic
- Mechanism: When a conflict occurs, TeamCity commits a new folder to your repository named
patches. Inside, you will find abuild.ktsfile representing the change - Resolution: These files are not automatically merged. You must follow these steps:
- You must manually review the patch file to understand the required setting change, then update your primary
settings.ktsfile to reflect that change - Delete the
patchesfolder entirely to apply the fix and resolve the state mismatch - Commit and push the latest changes
- You must manually review the patch file to understand the required setting change, then update your primary
This approach works well because we treat the VCS with the kotlin DSL as the source of truth, and any changes made in the UI as a "nice suggestion" we can choose to include in the code as config or not.
Advanced best practices
Level 1 abstraction - wrapper functions
What we’d ideally like is to have every build configuration automatically have the Build Files Cleaner feature, without having to manually add it. In order to do this, we could introduce a function that wraps every instance of BuildType with this feature. In essence, instead of having the Project call
buildType(Build)
buildType(AnotherBuild)
buildType(OneMoreBuild)
we would have it call
buildType(cleanFiles(Build))
buildType(cleanFiles(AnotherBuild))
buildType(cleanFiles(OneMoreBuild))
For this to work, we’d need to create the following function
fun cleanFiles(buildType: BuildType): BuildType {
buildType.features {
swabra {}
}
return buildType
}
The new function essentially takes a BuildType, adds a feature to it, and then returns the same BuildType instance.
We can improve the code a little so that it only adds the feature if it doesn’t already exist:
fun cleanFiles(buildType: BuildType): BuildType {
if (buildType.features.items.find { it.type == "swabra" } == null) {
buildType.features {
swabra {
}
}
}
return buildType
}
You can generalize this with lambda receivers:
fun wrapWithFeature(buildType: BuildType, featureBlock: BuildFeatures.() -> Unit): BuildType {
buildType.features {
featureBlock()
}
return buildType
}
Then you can use it like so:
buildType(wrapWithFeature(Build){
swabra {}
})
Level 2 abstraction - classes
You can create custom classes that inherit from the teamcity constructs as to provide a more configurable approach and avoid code duplication:
- Create a subclass of a
BuildTypeto create a custom build configuration class you can override with specific constructor arguments:
class Build(val os: String, val jdk: String) : BuildType({
id("Build_${os}_${jdk}".toExtId())
name = "Build ($os, $jdk)"
vcs {
root(DslContext.settingsRoot)
}
steps {
maven {
goals = "clean package"
mavenVersion = defaultProvidedVersion()
jdkHome = "%env.${jdk}%"
}
}
requirements {
equals("teamcity.agent.jvm.os.name", os)
}
})
- You can now register build configurations in a more flexible way by instantiating and registering those build configuration subclasses instead within the project:
val operatingSystems = listOf("Mac OS X", "Windows", "Linux")
val jdkVersions = listOf("JDK_18", "JDK_11")
project {
for (os in operatingSystems) {
for (jdk in jdkVersions) {
buildType(Build(os, jdk))
}
}
}
IntelliJ with Kotlin DSL
Cloning down project and installing dependencies
The nice part about IntelliJ is that you can isntall the dependencies from the pom.xml with maven to get type completion and intellisense for the DSL code.
NOTE
This works because the .teamcity folder is a Maven module.
- Clone down the project

- Right-click on the pom.xml file and select Add as Maven Project – the IDE will import the Maven module and download the required dependencies.

Installing documentation
This is how you can install documentation for type hinting and code completion:
Local testing
Local config validation
The pom.xml file provided for a Kotlin project has the generate task which can be used to generate TeamCity XML configuration files locally from the DSL scripts. This task can be started from IDE (see Plugins | teamcity-configs | teamcity-configs:generate node in the Maven tool window).
The generated TeamCity Kotlin DSL Maven project can generate the corresponding configuration locally using:
mvn teamcity-configs:generate
The result of the task execution will be placed under the .teamcity/target/generated-configs directory.
.teamcity/target/generated-configs
NOTE
Generation performs DSL validation, which can catch missing mandatory settings before TeamCity applies them.
So a useful development cycle is:
Edit DSL
↓
IDE compile/autocomplete
↓
mvn teamcity-configs:generate
↓
review Git diff
↓
commit
↓
TeamCity applies settings
You can also validate the teamcity config in your current project in the IDE via the teamcity project validate CLI command, learn more in [[#validating versioned settings]].
Running Kotlin DSL build off another branch
Let's say you want to test a new Kotlin DSL change for a project whose codebase is supabase-proj, so you push your changes to a branch called kotlin-dsl-test and then you want to create a test build using the DSL configuration from that branch.
Is that possible? Yes, but only in two ways:
- Method 1 - feature branches: listen for branch pushes on configured VCS roots and then create a build from one of those branches.
- Caveat: only works if your
.teamcityconfig as code is in the same repo as your codebase, meaning your VCS root and versioned settings configured VCS root are the same.
- Caveat: only works if your
- Method 2 - create a test project (recommended): Create a "Testing Zone" project and then add a subproject that uses a VCS root listening for the
kotlin-dsl-testbranch for its versioned settings.- This is the simplest and foolproof way to do it.
IMPORTANT
The most important thing to understand when deciding between these two methods is that feature branches are only possible if your team's config-as-code is in the exact same repo as your codebase that you're trying to do the CI with.
- choose feature branches when: If you're using the exact same VCS root for your version settings and your build configurations, then you can go ahead and use feature branches.
- choose test project when: Otherwise you need to do a test project if you separated your config as code from your codebase.
Branch-specific build configuration settings with Feature branches
With the help of versioned settings, you can create build configurations with variable settings for every repository branch via feature branches.
IMPORTANT
Use feature branches when the application source and .teamcity settings are on the same branch name and in the same repo.
main → normal source + normal settings
feature/my-change → feature source + changed settings
Why does this work? Because of this rule (see [[#Feature branches]] for more info):
when a logical branch exists for one VCS root but not another, the root missing that branch uses its default branch.
Look at this specific example:
Let's go more into depth:
Supa_Source_Root
repository: supa
default branch: refs/heads/main
branch spec: refs/heads/feat-*
Supa_Settings_Root
repository: supa-ci
default branch: refs/heads/main
branch spec: refs/heads/test-*
Supa Project
│
├── Versioned Settings
│ VCS Root = Supa_Settings_Root
│ Kotlin DSL
│
└── Supa Build
VCS Root = Supa_Source_Root
Suppose both repos contain the logical branch test-new-step:
supa/test-new-step
supa-ci/test-new-step
If both roots participate in branch resolution with that logical name, TeamCity matches them:
TeamCity branch:
test-new-step
│
┌────┴────┐
↓ ↓
supa supa-ci
↓ ↓
test-* test-*
IMPORTANT
TeamCity tries to correlate roots using logical branch names. If a root doesn't have the corresponding branch, its default branch can be used.
So when adding branch specs to let TeamCity recognize multiple branches for the VCS root connection, just make sure you name your branches intentionally.
Logical TeamCity branch
test-new-step
│
┌───┴──────────┐
│ │
▼ ▼
supa source supa-ci settings
test-new-step? test-new-step?
│ │
NO YES
│ │
▼ ▼
default branch matching branch
│ │
▼ ▼
main test-new-step
So the resulting revisions can conceptually be:
SOURCE:
supa/main
SETTINGS:
supa-ci/test-new-step
For more info, see this convo:
Here is the mental model to keep:
TeamCity
logical branch
"test-foo"
│
┌──────────┴──────────┐
│ │
▼ ▼
supa root supa-ci settings
│ │
test-foo exists? test-foo exists?
│ │
NO YES
│ │
▼ ▼
default → main test-foo
│ │
└──────────┬──────────┘
▼
BUILD USES
CODE SETTINGS
main test-foo
setup
Here's the main setup of using feature branches with Kotlin DSL:
- Configure the project’s Versioned Settings as: "When build starts: use settings from VCS" and also apply changes in snapshot dependencies.
Synchronization:
Enabled
Project settings VCS root:
Supa_Settings_Root
Settings format:
Kotlin
When build starts:
use settings from VCS
Apply changes in snapshot dependencies and version control settings
- Then start a build of
feature/my-change(assuming that's the branch with your new DSL). TeamCity reads.teamcityfromfeature/my-changeand runs the build using that branch’s settings.
manually running builds
You can manually run a build on a specific branch in one of the two ways:
-
Click Run opposite the required branch in the build list.
-
Open the Custom Run dialog, go to the Changes tab, and choose the required branch in the " Build branch " drop-down menu.
To run builds from a specific branch or set of branches automatically, configure build triggers or VCS triggers
Creating a test project
Have a TeamCity administrator create a temporary/sandbox subproject with:
- Versioned Settings VCS root:
git@gitlab.compusearch.com:devops/teamcityci/prismci.git - Settings branch:
fixing-disparity-svn-git - Settings directory:
.teamcity - Kotlin DSL format
- “When build starts”:
use settings from VCS
Here's an example recommended setup:
Testing Zone / PRISM Git DSL Disparity Test
│
├── Versioned-settings VCS root
│ └── prismci @ fixing-disparity-svn-git
│
└── DSL-defined build source roots
└── Prism @ Release_7.6.5.86
This neatly separates the two concerns:
- TeamCity reads Kotlin settings from your pushed feature branch.
- The build checks out the exact PRISM source branch you want to validate.
- No production TeamCity settings change.
- No need to create the same feature branch in
prism/Prism.git. - This should be a normal manually started test build, not a personal/local-changes build.
Here are the most important rules to understand in order to make this process successful:
- versioned control settings: Always do "When build starts: always use current settings" for the build start settings in version control, since you want the branch settings to override the DSL settings.
Here are the steps to do it:
- Create a new empty container project called
TestingZone - Create and push a branch with new DSL changes:
cd prismci
git switch master
git pull --ff-only
git switch -c fixing-disparity-svn-git
# Edit .teamcity/DbTemplate3_1.kt:
# - add the SF Current/Deltas validation step
# - restore cssutilities artifact rules, if included in this change
git add .teamcity
git commit -m "Validate Git SF database payload"
git push -u origin fixing-disparity-svn-git
- Validate teamcity config
teamcity project settings validate
- Create a new subproject in the
TestingZoneparent project
teamcity project create "PRISM Git DSL Disparity Test" `
--id TestingZone_PrismsGitDisparityTest `
--parent TestingZone
- Within that subproject, create a new VCS root and point to your branch with the DSL changes.
teamcity project vcs create `
--project TestingZone_PrismsGitDisparityTest `
--name "PrismCI - fixing-disparity-svn-git" `
--url "git@gitlab.compusearch.com:devops/teamcityci/prismci.git" `
--branch "refs/heads/fixing-disparity-svn-git" `
--branch-spec "+:refs/heads/fixing-disparity-svn-git" `
--auth ssh-key `
--ssh-key-name "tc_gitlab.id_rsa" `
--json
- Copy the returned VCS-root ID.
- Enable Kotlin versioned settings for the test project
teamcity project settings enable TestingZone_PrismsGitDisparityTest `
--vcs-root <returned-vcs-root-id> `
--format kotlin `
--settings-path .teamcity
- Then open the project’s Versioned Settings page and set:
Synchronization: enabled
Settings format: Kotlin
When build starts: always use current settings
NOTE
Why not “use settings from VCS” here? Your build source branch is Release_7.6.5.86, whereas your settings branch is fixing-disparity-svn-git. “Use settings from VCS” would attempt to select settings based on the build branch and defeats the separation you intentionally created.
- Confirm TeamCity imported your branch:
teamcity project settings status TestingZone_PrismsGitDisparityTest
- Run a build with those new DSL settings by specifying
--settings currentflag in order to toggle on the "always use current settings" option.
teamcity run start <job> `
--settings current `
--clean `
--watch
Production-grade setup
- Create a testing zone branch template that you fork off of when creating new test branches for Kotlin DSL. Call the branch something like
testing-zone/template. - Create a new branch in the pattern
testing-zone/*and commit your Kotlin DSL changes to that branch - Edit the VCS versioned settings to be as follows:

- Now create a custom build and make sure to load DSL settings from the testing zone branch

Teamcity AI
Teamcity MCP
This is how you add the teamcity MCP server to your AI agent:
- Get your teamcity auth token, and then set it in the current environment as the
TC_AUTH_TOKENenv var
export TC_AUTH_TOKEN="your token here"
- Add this MCP json, pointing to your teamcity server URL if self hosted
{
"mcpServers": {
"TeamCity nightly": {
"type": "http",
"url": "<TeamCity-server-URL>/app/mcp",
// Skip setting up auth settings
//"headers": { "Authorization": "Bearer $TC_AUTH_TOKEN" }
}
}
}
- Authenticate with your harness:
codex mcp login <server-name>
Look here for more examples like how to do it with Claude, etc.
Teamcity REST API
Octopus Basics
How Octopus works
Servers, tentacles, calamari
Octopus has these components:
- octopus deploy server: stores all CD info and configurations, communicates with the deploy tentacles to perform tasks.
- octopus deploy tentacle: manage of execution of deployment pipelines and to deploy the final result to target servers. There are three types of tentacles:
- listening tentacle: long-running process on a cloud server, or running on port 10933 locally if self-hosting, listens for commands from the deploy server and then executes those commands.
- polling tentacle: periodically connects to the Octopus deploy server and polls it for new commands to execute.
- worker tentacle: performs tasks on behalf of Octopus Deploy, like executing scripts
- calamari: what does the actual legwork of executing the deploy pipeline, controlled via tentacles. Tentacle can run multiple instances of calamari to perform multiple tasks in parallel.
NOTE
The important thing to understand here is that you'll only use Tentacles mainly and install them on virtual machines if you're hosting on-premises. If you own the virtual machines and infra, then you would need to install Tentacles on those virtual machines.

NOTE
You must put your tentacles on your target servers. If there is a server you want to deploy something to, then it must have a tentacle on it.
NOTE
In Octopus Deploy, Workers, Tentacles, and Calamari are core components that work together to execute your deployment processes. Here is how they differ:
- Tentacles: A Tentacle is an agent installed on a deployment target (a server or machine) that communicates with the Octopus server to receive and execute instructions. It essentially acts as the "arms" that reach out to your infrastructure to carry out tasks (34:25-35:50).
- Workers: A Worker is a specialized Tentacle used for executing tasks on behalf of the Octopus server rather than deploying directly to a specific target. Think of them as "emissaries" that perform actions like standing up infrastructure, checking connections, or running scripts in isolated environments, such as a DMZ or a cloud region (20:38-21:20, 34:00-34:35).
- Calamari: Calamari is the underlying command-line tool invoked by a Tentacle during a deployment. While the Tentacle provides the connection, Calamari does the actual heavy lifting—such as extracting NuGet packages, installing files, and running deployment scripts (like
Deploy.ps1).
Hierarchy
- spaces: acts like a workspace, contains project groups, projects, and environments.
- environments: stuff like
dev,qa, etc., which you can use to tag deployment targets with. - project groups: organize projects
- projects: contain the configuration for deployments
-> Spaces
-> project groups, environments
-> project
-> deployment process, channels, lifecycles
Spaces
Octopus Deploy offers an analog to workspaces called Spaces, which allows you to organize your projects and project groups into different workspaces.
In Octopus Deploy, spaces serve as a top-level organizational concept used to provide clean separation and isolation between different parts of your deployment workflows
Key aspects of spaces include:
- Isolation: They allow you to segment projects, environments, permissions, and access controls away from other areas of the application (10:21-10:40).
- Organizational Flexibility: You can treat a space as a dedicated container for a specific set of projects, managing them with their own unique set of admins and managers (10:43-11:05).
- Clean Environment: When you create a new space, it provides an isolated, empty environment to begin configuring your specific deployment needs without interference from other teams or project groups (11:07-11:13).
Environments
Octopus Deploy allows you to create several environments, like Dev, Test, and QA, which allow you to specify target environments to deploy to for the same package.
- Definition: Environments are semantic groupings of your deployment targets that represent different stages of your deployment pipeline.
- Purpose: They help organize targets so you can manage releases as they move through your infrastructure.
For practical use, apply the same environments across multiple projects rather than creating unique sets for every project.
- Naming: Use common company terminology, such as Development (dev), Test (qa), Staging (pre-prod), and Production.
- Abbreviations: Only use industry-standard abbreviations (e.g., QA) to avoid team confusion.
NOTE
In Octopus Deploy, managing deployment targets is primarily achieved by organizing them into Environments. Environments act as containers for your targets, representing the different stages of your deployment pipeline
- On the environments page, you can see how many deployment targets are assigned to each environment
- for example, you might see that your Development environment contains three targets, while your Test environment holds eleven.
Deployment targets and roles
Prereqs: [[#Creating deployments]].
- Deployment Targets: The specific machines, services, or environments (e.g., Windows/Linux servers, Azure, AWS, Kubernetes) where applications are deployed.
- Environments: The stages in a software lifecycle (e.g., Development, Test, Production) where targets are assigned.
- Roles: Descriptive tags used to identify the function of a deployment target (e.g., "Web Server"). Roles allow specific steps in a deployment process to execute on relevant targets.
When registering a deployment target there are two things that must be specified:
- environments: the environments to tag the deployment target with, meaning that only when deployment processes run on those environments will the deployment target be deployed to.
- target roles: Descriptive tags used to identify the function of a deployment target

During a release, Octopus checks which deployment targets match the assigned environments and roles defined in the deployment step, ensuring the application is deployed to the correct deployment target.
NOTE
Basically you can use the combination of both roles and environments to get really granular with how you select specific deployment targets a step in a deployment process applies to.
roles in depth
Roles are assigned to deployment targets and dictate how Octopus Deploy selects machines during a deployment or runbook run.
- Generic Roles: Describe server types (e.g., IIS Server 2019).
- Specific Roles: Describe application-specific functions (e.g., Hello World API).
A deployment target can have multiple roles:

If a step is assigned multiple roles, Octopus Deploy uses logic to select targets.
- OR Logic: Multiple roles on a single step are treated as an OR statement. If a server has either role, it is selected.
- Target Assignment: Assigning both UI and API roles to a server allows it to be picked up by multiple different project steps.
Best practices for roles
- Specific Roles: Use for deployments and application-specific runbooks to cleanly model environments.
- Generic Roles: Use for maintenance runbooks (e.g., OS updates, software installation), by targeting by software/VM type, like
nginxorwindows-server-2019 - Dual Assignment: It is recommended to assign both generic and application-specific roles to a single target to maximize utility.
Projects
- Projects: Used to define deployment processes, runbooks, and variables to deploy software across defined environments.
- Project Groups: Used to organize related projects, typically by application, to keep your instance tidy.
Here is the general process that showcases the hierarchy of what you're mainly going to do in Octopus:
- Create a project group
- Within that project group, create a project
- Within that project, create a new deployment
Tentacles and deployment targets
Adding tentacles
Here are the steps to add a tentacle on a Windows Server VM you own to make that VM a deployment target:
- Go to the deployment targets tab. Click on Add Deployment Target

- Choose the deployment target as Windows and choose a listening tentacle type:
3. Download the powershell script to install the tentacle, remember the thumbprint, then SSH into your windows server, and then run the powershell script

- Specify environment and role
- environment: the environment(s) to deploy on, like Dev or QA. This tells Octopus that we only want to deploy to this deployment target when a deployment process runs on that environment.
- target role: labels that you can then programmatically reference to target certain tentacles only for deployment. They describe the functionality of the target and are used by the deployment process
5. Upgrade calamari as a good practice. The first time you deploy a tentacle, you should upgrade calamari

- Now in your deployment process, target your deployment target via the target role

Best practices of creating deployment targets
- Naming Conventions: Use descriptive names for roles based on application names and server functions (e.g., Hello-World-Web-Server).
- Tentacle Selection: Prioritize Listening Tentacles over polling unless network restrictions necessitate otherwise.
- Environment Flexibility: A single deployment target can be mapped to one or multiple environments (e.g., a server shared between Development and Test).
Deploying packages directly to tentacles
Deployment in Octopus Deploy requires special rules for packages (same thing as build artifacts):
- file type: package type should be either a
.zipfile or a.nupkgfile - naming: the package name must follow a standard convention including the package name and package version, in this syntax:
package_name: the package name, alphabeticpackage_version: the package version, in the syntax<version_number>-<tag-selector>, like10.2.1-releaseis a valid package version with tag.
<package_name>.<package_version>.<file-extension>

A common approach where tags come in handy is when we have different branches and we want to create and deploy packages based on those different branches. We would use the branch name as the tag name, like the below example:

Connecting TeamCity to Octopus
Here's a high level overview of how it works:
- Whether you're using the TeamCity Kotlin DSL or manually creating a build step in TeamCity to publish to Octopus Deploy, you need to install the Octopus Deploy plugin for TeamCity in order to do any of this.
- You then need to add API key from Octopus to the TeamCity project root config so you actually have authorization to publish to Octopus directly via TeamCity.
Here are the steps in depth
- Install a plugin on TeamCity so it can connect to Octopus Deploy

- Go to your plugins list and enable the uploaded plugins:

- Go to Octopus Deploy and create a new API key.

- In your build configuration, add an extra step whose step type is OctopusDeploy: Push Packages, and specify the following info:
- Octopus server URL: Octopus server URL is either going to be on the cloud or some self-hosted IP address.
- API key: The OctopusDeploy API key you created
- Space name: the name of the space to deploy to on your Octopus Server.
- package paths: the artifact mapping from your source code to the one produced by team city
- publish packages as build artifacts: if checked, uses the package paths configuration as the naming scheme to find packages and publish them.

- From your general settings of your project, remove the artifact paths to avoid overriding the ones you specified with Octopus Deploy:

Octopus projects overview
Here's an overarching mental model:
Project
│
├── Deployment process
│ └── Defines WHAT Octopus does
│
├── Channel
│ └── Defines WHICH release lane is used
│
├── Lifecycle
│ └── Defines WHERE and in WHAT ORDER the release goes
│
└── Release
└── A deployable snapshot of packages, process, and variables
Configuring dashboard to view project groups
You can select which project groups and projects are visible in the dashboard by following these steps:
- Search for and then click on configure dashboard

- Configure the dashboard

Creating projects
You can create projects in Octopus, but it requires two important details:
- project group: the project group to scope the project to.
- lifecycle: the default lifecycle to set for the project.

Users, roles, permissions, and teams
- permissions: A single policy for an action
- Example: creating a project, creating a release, running a deployment process
- roles: a grouping of one or more permissions
- Example: project deployer role would be able to create projects, releases, and deployment processes.
- teams: Each team contains one or more roles, and is thus a large grouping of permissions
- users: users are added to teams and then assume the permissions of the roles within the team

Octopus Deployments
Intro
In Octopus Deploy, the normal design is:
- Project/process = how DIS is deployed
- Environment = where it is deployed, such as Dev, QA, or Production
- Release = a versioned snapshot of the process, packages, and variables that gets promoted through those environments
Here's an overarching mental model:
Project
│
├── Deployment process
│ └── Defines WHAT Octopus does
│
├── Channel
│ └── Defines WHICH release lane is used
│
├── Lifecycle
│ └── Defines WHERE and in WHAT ORDER the release goes
│
└── Release
└── A deployable snapshot of packages, process, and variables
A useful shorthand is:
- Process: What deployment actions happen?
- Environment: Where does the application run?
- Lifecycle: In what order may environments receive a release?
- Channel: Which release strategy or lane does the release follow?
- Release: Which version is being deployed?
Creating deployments
A deployment requires these components:
- Deployment Process: A set of steps defined within an Octopus Deploy project that the server runs to deploy software.
- Channel: versioning and lifecycle integration for package deployment.
- Release: versioned snapshot of the deployment process and the associated artifacts/assets as they existed when the release was created.
Creating a deployment process

- Add a step to the deployment process

- Configure the step to run on specific deployment targets.

Email notifications
To add email notifications into the deployment process, just use the Send an email step:

Here are some best practices when using the email-sending action:
- use variables for email addresses: it's best to use a variable for the destination email address since you may want to send to different email addresses depending on the current environment the release or deployment process is executing for.
- use the environment name for dynamic emails: Use the system variable
Octopus.Environment.Namefor dynamic emails for different environments
Adding a manual intervention step
Adding a manual intervention step into the deployment process is a great way for us to require a human in the loop and notify people when pushing or creating an important deployment process execution in something important like prod.
That's when something like a manual intervention step would be necessary and then an Octopus administrator can step in and approve the deployment process execution to go ahead.
Deployment examples
Octopus and creating an IIS pipeline
When creating an octopus project, you can configure the pipeline with prebuilt step recipes, and Octopus offers a prebuilt step for deploying to IIS app pools.
- Choose the "Deploy to IIS" prebuilt step template:

- Configure the deploy to IIS step by specifying the target environment, which will automatically deploy to the IIS app pools on all tentacles tagged by that target environment.
3. Select the specific package (artifact) to deploy

Releases
Releases are a versioned snapshot of the deployment process and the associated artifacts/assets as they existed when the release was created.
From a single versioned release, you can deploy to different environments, which then deploys to the actual deployment targets/tentacles.
NOTE
The primary philosophy of Octopus Deploy is that how you deploy to dev and test should be the same way you deploy to production. You can do this by creating a release abstraction and the release abstraction is the thing that actually handles the individual configuration and parameter injection to deploy to test, dev, and prod with a unified interface.
Here are the rules:
- You can have one release generator per project
- You can create releases either from deployment processes or from packages.
Here are the prereqs:
- deployment process: Can't release shit without a deployment process.
Ok now here are the steps:
- Click on Create Release button

- Specify version and release notes

- Choose the lifecycle (see [[#Lifecycles]]) that you want this release to follow, and then deploy it to the first environment in the lifecycle

- Choose to deploy now or schedule later, then click the Deploy button to start the deployment

- Once it starts to get deployed, you can view the deployment pipeline status and progress via the task list, which gives complete observability over the individual deployment process steps.

Creating a release from a package
- Create a release from a package, sele

Variables
Variables allow your applications to run in deployment pipelines and get deployed to different environments and infrastructure without having to manually hardcode parameters or config settings.
- Purpose: To parameterize deployment processes and runbooks.
- Benefit: Enables applications to function across various infrastructure stages without hardcoding or manually updating configuration settings.
There are two types of variables in Octopus Deploy:
- system variables: Variables managed by Octopus, available under the
Octopusnamespace, available system wide or specific ones that inject project-specific values. - project variables: user-created variables that you make available at the project level. You can further constrain the use of these variables by scoping them to certain environments, target roles, target servers/tentacles, and specific deployment steps.
- Scopes allow you to supply different variable values for each scope, namespaced under a single variable.
You can add variables for a project via the project variables tab, which lets you use the variable anywhere inside the project.
IMPORTANT
Once you create variables, you can use them anywhere when configuring Octopus pipelines or releases via template string interpolation, with the #{} syntax.
- Create a project variable
2. You can also scope the variable to restrict its use more and provide conditional, scoped values for the variable.


- Use the variable to make steps in a deployment process more dynamic:

Extra variable configuration
If you want extra variable configuration, you have these options:
- Edit an existing variable
2. Add configuration, like dropdowns, prompts, etc.

A caveat here is that if you decide to provide extra configuration for your variable and prompt for its value, then for project scopes you can only scope by environment and nothing else (because that's the only thing known beforehand).

System variables
Here are the important namespaces:
Octopus.Environment: object with info about the environment being deployed to.Octopus.Action.Package: object with info about the current package that is trying to be deployed.Octopus.Deployment: object with info about deployments
Variable interpolation and filters
You already know that you can use template string interpolation with the #{} syntax to retrieve the values of variables and inject them into a string.
But you can also invoke functions on those variables and transform them before they get interpolated, using filters:

THen you can use them in this syntax:
#{variableName | filterFn }

Lifecycles
A lifecycle defines the path that a release can take through deployment environments.
Think of it as the project’s promotion roadmap:
Development → QADEMO → QA
A lifecycle can control:
- The environments available to a release
- The order in which releases move through environments
- Whether an environment or phase is required or optional
- Whether deployment to an environment begins automatically
- How many releases and deployment files are retained
Octopus calls each stage in a lifecycle a phase, where a phase can contain one environment or multiple environments.
- You can configure a lifecycle to require a successful deployment to an earlier phase before a later phase becomes available.
- In the case of multiple environments in a phase, a deployment process has to successfully complete for all of those environments in order to graduate to the next phase.
NOTE
Think of a phase as a stage in your product life cycle that your application is deployed to.
By default, Octopus creates a default lifecycle for each project which creates a phase for each environment you have and creates a sequential ordering of those phases.


Creating lifecycles
- Go the the lifecycles tab, then click on create lifecycle, then name the lifecycle:

- Add a new phase, filling in this info:
- phase name: how the phase should be named
- environments: a list of environments that make up the phase
- required to progress: the strictness of how many environments need to have successful deployment process executions within the phase before you consider a phase successful and then you can pass on to the next one.

- For each environment you add, you can specify if you want to automatically deploy to the deployment targets tagged by that environment or if you need to manually deploy to that environment via clicking a button.

- Finish adding all the phases the same way, then save the lifecycle.
- Change the lifecycle of your deployment process to your newly created lifecycle


Channels
NOTE
Channels are lifecycles for packages.
A channel is a release lane inside a project, where we can enforce lifecycles for packages based on the branch, not just on the environment like lifecycles do.
Channels let one Octopus project support different release strategies without duplicating the project. Every release belongs to a channel, and the channel can determine:
- Which lifecycle the release follows
- Which package versions are allowed
- Which process steps run
- Which variables apply
- Which tenants apply
NOTE
So what makes channels different to life cycles?
With channels you can get even more granular than life cycles and specify additional rules that will do basically the same thing as life cycles (where you force a certain progression in phases), but you can do it based on the package name that you're trying to deploy.
NOTE
Each channel can behave differently while still using the same underlying project.
Octopus supports channel-specific lifecycles, process steps, variables, tenants, and package-version rules.
Every project has a default channel, and additional channels can be created when genuinely different release behavior is needed.

Creating a new channel
- Create a new channel and choose its lifecycle

- Add package version rules for creating releases in the channel, apply the rule to steps in the deployment process, then create the channel.
- package steps: the deployment process steps where the package name is available
- version range: a certain range of versions to include for the channel
- pre-release tag: the tag on the package branch, where only the packages with this tag will get deployed to the channel.

Channel version ranges
Here is the syntax for defining the version ranges for channels:

Creating a release from channels
Now that you created a channel, you can use channels in your releases to further scope which packages will actually get deployed and follow a lifecycle as well.
NOTE
The advantages of creating a channel and then using that channel for releases are that now you have further scopes and filters on what packages are even available to get deployed at each stage, sort of like life cycles but for packages.
- Select the channel to use for creating a release

All together
Updating deployments
Here are the most important tips to understand with deployments from channels:
IMPORTANT
Whenever the process steps of a deployment get updated you will need to create a new release in order to incorporate those changes.
Here is a list of what you need to do after you update a deployment step template
- Update the step in your deployment process that is based on the deployment step template
- Create a new deployment.
Tenants
Tenants are an extra layer over environments where a single consumer or developer can get a parameterized individualized version of an environment deployment.

Here are the rules of tenants:
- Each tenant must be associated with a project and at least one environment.
- You can provide variable templates for each tenant to parameterize them at runtime and inject each tenant with individual values
Here's how to create a tenant:
- Go to the Tenants tab and click on the Add Tenant button.

- coNNECT THe tenant to a project and environment
3. Go to project templates within a project, and then click on the "add template" button to add a variable for a tenant, then repeat this for as many variables as you need.

- Specify the name and variable type for the variable you want to add

- Go to the tenants tab and then fill out the variable values for each individual tenant for whom you created a project variable template in a project the tenants are connected to.

- Now you have to configure tenanted deployments for the deployment targets. Go to Infrastructure -> Deployment targets then click on the individual deployment target(s) that are deployment targets for the projects and environments you tenants are connected to.

- Change the tenanted deployments setting on the deployment target to include both tenanted and untenanted deployments:
8. Associate this deployment target with the specific tenants you want to deploy for, then hit save.

- When creating a release, choose the tenants you want to deploy to.

Octopus REST API
Creating an API key
With an Octopus API key, you can connect Octopus with TeamCity directly and also use the Octopus Server REST API .
- Click on your profile image, then go to My Profile

- Once in your profile, click on My API keys and then create a new API key

- Create a variable with the value of it being the Octopus API key, and then put that variable into a global variable set that can be accessed across all projects.

Swagger UI
If you are self-hosting Octopus, you can view the Swagger UI representation of the Octopus REST API at this URL, following this template:
https://<your-octopus-domain>/swaggerui
https://cd.compusearch.com/swaggerui/index.html
API basics
API authentication
There are two ways to authenticate with the API:
- Method 1 - query param: attach the
ApiKey=query param and provide the value as the API key
@octopusServer = https://ashdevdeploy01.compusearch.com
@apiKey = "your api key here"
GET {{octopusServer}}/api/environments/all?ApiKey={{apiKey}}
- Method 2 - with headers: pass in the API key as the value for the
X-Octopus-ApiKeyheader like so:
$headers = @{
"X-Octopus-ApiKey" = $apiKey
"Content-Type" = "application/json"
}
$envs = Invoke-RestMethod "$octopusServerUrl/api/environments/all" -Headers $headers
API resources
Service accounts
Service accounts allows non-human entities (like build servers) to interact with the Octopus Deploy RESTful API securely while obeying the principle of least privilege.
NOTE
It's sorta like Github apps
- Login Restrictions: Service accounts cannot log in to the Octopus Deploy UI using a password.
Here's the overview:
- Create a service account for a use case, then save the API key
- Add a service account to a team to give it permissions, accounting for the principle of least privilege
- Use the service account API key to access the REST API, granted with the permissions it gained from the team it was added to.
Forbidden Knowledge from Michael Jordan (Joseph Dempsey) to Lebron James (Rohit Ramakrishnan) to Tony Snell (Aadil Mallick), with special appearances by Allen Iverson (Clint Smith)
Teamcity to Veracode
- Go to your organization's dashboards in veracode
2. Click on the one named "findings, filtration, customization date"
3. Filter the table for high and very high vulnerabilities within the past month:

- Download the data
