โ† Back to DevBytes

TeamCity: Complete Configuration Guide

TeamCity: Complete Configuration Guide

TeamCity is a powerful, Java-based continuous integration and continuous deployment (CI/CD) server developed by JetBrains. It helps development teams automate building, testing, and deploying applications across various platforms and languages. Whether you are running a small startup or managing enterprise-scale infrastructure, TeamCity provides a flexible, plugin-rich environment that integrates seamlessly with popular version control systems, build tools, and cloud providers.

Why TeamCity Matters

In modern software development, manual builds and deployments are error-prone and slow. TeamCity addresses this by providing a centralized platform where every code change triggers an automated pipeline. This ensures that bugs are caught early, releases are predictable, and teams can ship software with confidence. Key benefits include:

Architecture Overview

TeamCity operates on a client-server architecture. The TeamCity Server is the central component that manages configuration, scheduling, and reporting. Build Agents are separate processes that execute the actual build and test jobs. A single server can coordinate dozens or even hundreds of agents, and agents can run on physical machines, virtual machines, or containers.

Installing TeamCity

TeamCity can be installed on Windows, Linux, or macOS. The simplest way to get started is using Docker, which bundles the server and an internal database. For production, you should connect an external database such as PostgreSQL or MySQL.

Running TeamCity with Docker

The following Docker Compose file launches a TeamCity server along with a PostgreSQL database and one build agent:

version: "3.8"

services:
  teamcity-server:
    image: jetbrains/teamcity-server:latest
    ports:
      - "8111:8111"
    volumes:
      - teamcity-data:/data/teamcity_server/datadir
      - teamcity-logs:/opt/teamcity/logs
    depends_on:
      - postgres

  postgres:
    image: postgres:15
    environment:
      POSTGRES_DB: teamcity
      POSTGRES_USER: teamcity
      POSTGRES_PASSWORD: secure_password
    volumes:
      - pg-data:/var/lib/postgresql/data

  teamcity-agent:
    image: jetbrains/teamcity-agent:latest
    environment:
      - SERVER_URL=http://teamcity-server:8111
      - AGENT_NAME=DefaultAgent
    depends_on:
      - teamcity-server

volumes:
  teamcity-data:
  teamcity-logs:
  pg-data:

Start the stack with the following command:

docker-compose up -d

Once running, navigate to http://localhost:8111 in your browser to complete the initial setup wizard. You will be prompted to choose a database type, create an administrator account, and accept the license agreement.

Core Concepts

Before diving into configuration, it is important to understand the core building blocks of TeamCity:

Configuring Your First Project

After installation, the next step is to create a project and connect it to a source repository. TeamCity supports Git, Subversion, Mercurial, Perforce, and Team Foundation Version Control out of the box.

Creating a Project from a Repository URL

The fastest way to set up a project is to let TeamCity auto-detect build steps from your repository. Navigate to Administration → Projects and click Create project. Choose From a repository URL and enter your Git URL:

https://github.com/your-org/your-repo.git

TeamCity will scan the repository and suggest build configurations based on detected files such as pom.xml, package.json, build.gradle, or Dockerfile. Review the suggestions and click Create.

Manually Adding a VCS Root

If you prefer manual configuration, go to your project settings and add a VCS root. The following example shows a Git VCS root configuration in the Kotlin DSL format, which can be stored in version control:

import jetbrains.buildServer.configs.kotlin.*
import jetbrains.buildServer.configs.kotlin.vcs.GitVcsRoot

project {
    vcsRoot(MainVcsRoot)
}

object MainVcsRoot : GitVcsRoot({
    name = "Main Repository"
    url = "https://github.com/your-org/your-repo.git"
    branch = "refs/heads/main"
    branchSpec = "+:refs/heads/*"
    authMethod = password {
        userName = "ci-user"
        password = "credentialsJSON:github-token"
    }
})

Defining Build Steps

Build steps are the heart of any build configuration. Each step runs a specific command or tool, and steps execute sequentially unless parallel execution is configured. TeamCity provides built-in runners for Maven, Gradle, MSBuild, npm, Docker, and more.

Example: Node.js Build Configuration

The following Kotlin DSL example defines a build configuration for a Node.js application. It installs dependencies, runs linting, executes unit tests, and builds the production bundle:

import jetbrains.buildServer.configs.kotlin.*
import jetbrains.buildServer.configs.kotlin.buildSteps.script
import jetbrains.buildServer.configs.kotlin.buildSteps.npm

object Build : BuildType({
    name = "Build and Test"
    vcs { root(MainVcsRoot) }

    steps {
        npm {
            name = "Install Dependencies"
            commands = "ci"
        }
        script {
            name = "Lint"
            scriptContent = "npm run lint"
        }
        script {
            name = "Unit Tests"
            scriptContent = "npm run test:unit"
        }
        npm {
            name = "Build"
            commands = "run build"
        }
    }

    artifactRules = "dist/** => dist.zip"
})

Example: Docker Build and Push

For containerized applications, you can build an image and push it to a registry within the same pipeline:

import jetbrains.buildServer.configs.kotlin.*
import jetbrains.buildServer.configs.kotlin.buildSteps.dockerCommand

object DockerBuild : BuildType({
    name = "Docker Build and Push"
    vcs { root(MainVcsRoot) }

    params {
        param("env.IMAGE_NAME", "my-app")
        param("env.REGISTRY", "registry.example.com")
        param("env.BUILD_NUMBER", "%build.counter%")
    }

    steps {
        dockerCommand {
            name = "Build Image"
            commandType = build {
                source = file {
                    path = "Dockerfile"
                }
                namesAndTags = """
                    %env.REGISTRY%/%env.IMAGE_NAME%:%env.BUILD_NUMBER%
                    %env.REGISTRY%/%env.IMAGE_NAME%:latest
                """.trimIndent()
            }
        }
        dockerCommand {
            name = "Push Image"
            commandType = push {
                namesAndTags = """
                    %env.REGISTRY%/%env.IMAGE_NAME%:%env.BUILD_NUMBER%
                    %env.REGISTRY%/%env.IMAGE_NAME%:latest
                """.trimIndent()
            }
        }
    }
})

Configuring Build Triggers

Triggers automate the execution of builds. The most common trigger is the VCS trigger, which starts a build whenever changes are detected in the connected repository.

VCS Trigger

import jetbrains.buildServer.configs.kotlin.triggers.vcs

object Build : BuildType({
    name = "Build on Commit"
    vcs { root(MainVcsRoot) }

    triggers {
        vcs {
            branchFilter = "+:<default>"
            quietPeriodMode = VCS_TRIGGER_QUIET_PERIOD
            quietPeriod = 60
        }
    }
})

The quietPeriod setting introduces a 60-second delay after the last commit before triggering a build. This is useful when developers push multiple commits in quick succession, as it batches them into a single build.

Scheduled Trigger

Scheduled triggers are useful for nightly builds, periodic integration tests, or cleanup tasks:

import jetbrains.buildServer.configs.kotlin.triggers.schedule

object NightlyBuild : BuildType({
    name = "Nightly Build"
    vcs { root(MainVcsRoot) }

    triggers {
        schedule {
            schedulingPolicy = daily {
                hour = 2
                minute = 0
            }
            branchFilter = "+:refs/heads/main"
            triggerBuild = always()
        }
    }
})

Managing Build Dependencies and Snapshots

Complex pipelines often require one build to depend on the output of another. TeamCity supports two types of dependencies: artifact dependencies, where a build downloads artifacts produced by another build, and snapshot dependencies, which ensure that dependent builds use the same source code revision.

Artifact Dependency Example

import jetbrains.buildServer.configs.kotlin.*
import jetbrains.buildServer.configs.kotlin.buildFeatures.artifacts

object Deploy : BuildType({
    name = "Deploy to Staging"
    vcs { root(MainVcsRoot) }

    dependencies {
        artifacts(Build) {
            artifactRules = "dist.zip => dist"
            buildRule = lastSuccessful()
        }
    }

    steps {
        script {
            name = "Deploy"
            scriptContent = """
                unzip dist/dist.zip -d /var/www/app
                systemctl restart my-app
            """.trimIndent()
        }
    }
})

Snapshot Dependency Example

object IntegrationTest : BuildType({
    name = "Integration Tests"
    vcs { root(MainVcsRoot) }

    dependencies {
        snapshot(Build) {
            onDependencyFailure = FailureAction.FAIL_TO_START
        }
    }

    steps {
        script {
            name = "Run Integration Tests"
            scriptContent = "npm run test:integration"
        }
    }
})

Working with Build Agents

Build agents are the workhorses of TeamCity. Each agent has a set of capabilities, including installed tools, operating system, and environment variables. TeamCity matches build configurations to compatible agents based on required capabilities.

Installing a Build Agent

To install a build agent on a Linux machine, download the agent ZIP from the TeamCity server and extract it:

wget http://teamcity-server:8111/update/buildagent.zip
unzip buildagent.zip -d /opt/teamcity-agent
cd /opt/teamcity-agent/conf
cp buildAgent.dist.properties buildAgent.properties

Edit buildAgent.properties to point the agent to your server:

serverUrl=http://teamcity-server:8111
name=Linux-Agent-01
workDir=/opt/teamcity-agent/work
tempDir=/opt/teamcity-agent/temp
systemDir=/opt/teamcity-agent/system

Start the agent:

cd /opt/teamcity-agent/bin
./agent.sh start

The agent will appear in the TeamCity UI under Agents → Unauthorized. Authorize it to begin assigning builds.

Defining Agent Requirements

You can restrict a build configuration to run only on agents with specific capabilities. For example, to require Docker on the agent:

import jetbrains.buildServer.configs.kotlin.*
import jetbrains.buildServer.configs.kotlin.buildFeatures.requirements

object DockerBuild : BuildType({
    name = "Docker Build"
    vcs { root(MainVcsRoot) }

    requirements {
        contains("teamcity.agent.jvm.os.name", "Linux")
        exists("docker.version")
    }

    steps {
        dockerCommand {
            name = "Build"
            commandType = build {
                source = file { path = "Dockerfile" }
            }
        }
    }
})

Handling Credentials and Secrets

Storing secrets in plain text is a security risk. TeamCity provides a built-in credentials vault that encrypts sensitive values such as API tokens, passwords, and SSH keys. Encrypted values are referenced using the credentialsJSON prefix.

Using Encrypted Parameters

First, create a project-level parameter of type Password in the TeamCity UI. Then reference it in your configuration:

params {
    param("env.NPM_TOKEN", "credentialsJSON:npm-publish-token")
    param("env.DATABASE_URL", "credentialsJSON:database-url")
}

In a build step, the token is injected as an environment variable without being exposed in build logs:

steps {
    script {
        name = "Publish Package"
        scriptContent = """
            echo "//registry.npmjs.org/:_authToken=\${NPM_TOKEN}" > .npmrc
            npm publish
        """.trimIndent()
    }
}

Notifications and Reporting

TeamCity can notify teams about build results through email, Slack, Microsoft Teams, and other channels. Notifications can be configured at the user level or globally for the entire project.

Slack Notification via Build Script

For custom notifications, you can use a build step that posts to a Slack webhook:

steps {
    script {
        name = "Notify Slack"
        scriptContent = """
            curl -X POST -H 'Content-type: application/json' \\
              --data "{\"text\":\"Build %system.teamcity.buildConfName% #%build.number% finished with status %system.build.status%\"}" \\
              https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX
        """.trimIndent()
        executionMode = BuildStep.ExecutionMode.ALWAYS
    }
}

Setting executionMode = BuildStep.ExecutionMode.ALWAYS ensures the notification runs even if previous steps fail.

Best Practices

Conclusion

TeamCity is a mature and feature-rich CI/CD platform that scales from simple build automation to complex, multi-stage deployment pipelines. By understanding its core concepts, leveraging the Kotlin DSL for configuration-as-code, and following best practices around security, performance, and agent management, development teams can build a reliable and efficient delivery workflow. Start with a basic build configuration, gradually add testing and deployment stages, and refine your pipeline over time as your project grows. With proper configuration, TeamCity becomes an indispensable tool that keeps your codebase healthy and your releases predictable.

๐Ÿ›  Tools from DevBytes

Inventory Tracker Pro โ€” Excel inventory system, low-stock alerts ยท $19
AI Dev Kit for Mac โ€” local AI dev environment templates ยท $9.99
KeyMapper for Mac โ€” custom keyboard shortcut toolkit ยท $7.99

โ† Back to all articles