TeamCity

CI/CDTeamCityJetBrainsEnterpriseKotlin DSLIntelliJCommercialOn-premises

CI/CD Tool

TeamCity

Overview

TeamCity is JetBrains' commercial CI/CD server. It features powerful build management, IntelliJ integration, and rich enterprise features, enabling modern DevOps through Kotlin DSL configuration and Docker/Kubernetes support.

Details

TeamCity is a commercial CI/CD automation server developed by JetBrains, expanding adoption in enterprise environments since its initial release in 2006. TeamCity 2025.03 was released in 2025, achieving significant UI updates through TeamCity/Pipelines integration, enhanced Docker/Podman support, and improved Perforce integration. While other CI/CD tools use YAML, TeamCity adopts Kotlin DSL, providing strong type safety and excellent developer experience. Tight integration with IntelliJ IDEA, Rider, and Visual Studio enables developers to interact directly with CI/CD systems from their IDEs. Intelligent build features automatically detect repository changes and trigger appropriate build steps, supporting incremental builds, dependency-based caching, and test history tracking. Kubernetes support dynamically launches build agents based on workload, with access to nearly all functionality through RESTful APIs. The free version supports up to 100 build configurations and 3 agents, including all enterprise features.

Pros and Cons

Pros

  • Kotlin DSL Configuration: Type-safe configuration without YAML
  • Powerful IDE Integration: IntelliJ, Rider, Visual Studio integration
  • Intelligent Builds: Automatic change detection and build triggering
  • Enterprise Features: Advanced security and governance
  • Docker/Kubernetes Support: Native container support
  • Rich VCS Integration: Git, Perforce, Mercurial support
  • Detailed Reporting: Comprehensive build analysis and metrics
  • Free Version Features: All features available up to 100 configurations
  • Experimental Kubernetes Executor: Native K8s pod execution

Cons

  • License Costs: High costs for large environments
  • Learning Curve: Kotlin DSL and proprietary concepts
  • JetBrains Ecosystem Dependency: Constraints in non-JetBrains IDE environments
  • Server Management: On-premises operation management overhead
  • Resource Consumption: High memory and CPU usage
  • Cloud Limitations: Limited fully managed service options
  • Community Size: Smaller community compared to open-source competitors

Key Links

Code Examples

Basic Kotlin DSL Configuration

// .teamcity/settings.kts
version = "2024.03"

project {
    buildType(Build)
    buildType(Test)
    buildType(Deploy)
}

object Build : BuildType({
    name = "Build"
    
    vcs {
        root(DslContext.settingsRoot)
    }
    
    steps {
        script {
            name = "Install Dependencies"
            scriptContent = """
                npm ci
                npm run build
            """.trimIndent()
        }
    }
    
    triggers {
        vcs {
            branchFilter = "+:*"
        }
    }
    
    features {
        dockerSupport {
            loginToRegistry = on {
                dockerRegistryId = "myregistry"
            }
        }
    }
})

object Test : BuildType({
    name = "Test"
    
    dependencies {
        snapshot(Build) {
            onDependencyFailure = FailureAction.FAIL_TO_START
        }
    }
    
    steps {
        script {
            name = "Run Tests"
            scriptContent = """
                npm test
                npm run test:integration
            """.trimIndent()
        }
    }
    
    features {
        coverage {
            tool = jacoco
            rules {
                rule {
                    minValue = 80
                    metric = LINES
                }
            }
        }
    }
})

Docker Integration and Multi-platform

// .teamcity/settings.kts
object DockerBuild : BuildType({
    name = "Docker Build"
    
    params {
        param("docker.image.name", "myapp")
        param("docker.registry", "myregistry.azurecr.io")
    }
    
    steps {
        dockerCommand {
            name = "Build Docker Image"
            commandType = build {
                source = file {
                    path = "Dockerfile"
                }
                namesAndTags = "%docker.registry%/%docker.image.name%:%build.number%"
                commandArgs = "--platform linux/amd64,linux/arm64"
            }
        }
        
        dockerCommand {
            name = "Push Docker Image"
            commandType = push {
                namesAndTags = "%docker.registry%/%docker.image.name%:%build.number%"
            }
        }
    }
    
    features {
        dockerSupport {
            loginToRegistry = on {
                dockerRegistryId = "azure-registry"
            }
        }
        
        buildCache {
            use {
                rules = """
                    +:**/node_modules
                    +:**/dist
                """.trimIndent()
            }
            publish {
                rules = """
                    +:**/node_modules
                    +:**/dist
                """.trimIndent()
            }
        }
    }
})

Kubernetes Deployment

// .teamcity/settings.kts
object KubernetesDeploy : BuildType({
    name = "Deploy to Kubernetes"
    
    params {
        param("k8s.namespace", "production")
        param("k8s.cluster", "production-cluster")
        param("app.image", "%docker.registry%/%docker.image.name%:%build.number%")
    }
    
    dependencies {
        snapshot(DockerBuild) {
            onDependencyFailure = FailureAction.FAIL_TO_START
        }
    }
    
    steps {
        script {
            name = "Update Kubernetes Manifests"
            scriptContent = """
                sed -i 's|IMAGE_TAG|%app.image%|g' k8s/deployment.yaml
                kubectl apply -f k8s/ --namespace=%k8s.namespace%
                kubectl rollout status deployment/myapp --namespace=%k8s.namespace%
            """.trimIndent()
        }
        
        script {
            name = "Health Check"
            scriptContent = """
                kubectl get pods --namespace=%k8s.namespace%
                kubectl logs deployment/myapp --namespace=%k8s.namespace% --tail=50
            """.trimIndent()
        }
    }
    
    features {
        kubernetes {
            connection = kubernetesConnection {
                name = "k8s-connection"
                apiServerUrl = "https://k8s-api.example.com"
                namespace = "%k8s.namespace%"
                authStrategy = token {
                    token = "%secure:k8s.token%"
                }
            }
        }
        
        notifications {
            notifier = slackNotifier {
                connection = slackConnection {
                    id = "slack-connection"
                    botToken = "%secure:slack.token%"
                }
                messageFormat = teamcityFormat()
                channel = "#deployments"
            }
        }
    }
})

Complex Workflows and Templates

// .teamcity/settings.kts
object PipelineTemplate : Template({
    name = "Standard Pipeline"
    
    params {
        param("app.name", "")
        param("git.branch", "main")
        param("environment", "staging")
    }
    
    vcs {
        root(DslContext.settingsRoot)
    }
    
    steps {
        script {
            name = "Setup Environment"
            scriptContent = """
                echo "Setting up %app.name% for %environment%"
                npm ci
            """.trimIndent()
        }
        
        script {
            name = "Run Quality Checks"
            scriptContent = """
                npm run lint
                npm run type-check
                npm audit
            """.trimIndent()
        }
        
        script {
            name = "Build Application"
            scriptContent = """
                npm run build
                tar -czf %app.name%-%build.number%.tar.gz dist/
            """.trimIndent()
        }
        
        script {
            name = "Run Tests"
            scriptContent = """
                npm test -- --coverage
                npm run test:e2e
            """.trimIndent()
        }
    }
    
    triggers {
        vcs {
            branchFilter = "+:%git.branch%"
        }
    }
    
    features {
        pullRequests {
            vcsRootExtId = "${DslContext.settingsRoot.id}"
            provider = github {
                authType = token {
                    token = "%secure:github.token%"
                }
                filterAuthorRole = PullRequests.GitHubRoleFilter.MEMBER
            }
        }
    }
})

// Template usage example
object WebAppPipeline : BuildType({
    templates(PipelineTemplate)
    name = "Web App Pipeline"
    
    params {
        param("app.name", "webapp")
        param("environment", "production")
    }
})

object APIPipeline : BuildType({
    templates(PipelineTemplate)
    name = "API Pipeline"
    
    params {
        param("app.name", "api")
        param("git.branch", "develop")
    }
})

Security and Enterprise Features

// .teamcity/settings.kts
object SecurePipeline : BuildType({
    name = "Production Deployment"
    
    params {
        param("env.VAULT_ADDR", "https://vault.example.com")
        password("vault.token", "%secure:vault.production.token%")
    }
    
    steps {
        script {
            name = "Security Scan"
            scriptContent = """
                # SonarQube analysis
                sonar-scanner \
                  -Dsonar.projectKey=%teamcity.project.id% \
                  -Dsonar.sources=src/ \
                  -Dsonar.host.url=%secure:sonar.url% \
                  -Dsonar.login=%secure:sonar.token%
                
                # Dependency vulnerability scan
                npm audit --audit-level high
                
                # SAST scan
                semgrep --config=auto src/
            """.trimIndent()
        }
        
        script {
            name = "Retrieve Secrets"
            scriptContent = """
                export VAULT_TOKEN=%vault.token%
                DATABASE_URL=$(vault kv get -field=url secret/app/database)
                API_KEY=$(vault kv get -field=key secret/app/api)
                
                echo "##teamcity[setParameter name='env.DATABASE_URL' value='$DATABASE_URL']"
                echo "##teamcity[setParameter name='env.API_KEY' value='$API_KEY']"
            """.trimIndent()
        }
    }
    
    features {
        approvals {
            approval {
                approvalRules = "user:admin,user:manager"
                timeout = 3600
            }
        }
        
        investigations {
            defaultAssignee = user("teamlead")
            defaultResponsible = user("devops")
        }
    }
    
    requirements {
        contains("teamcity.agent.os.name", "Linux")
        moreThan("teamcity.agent.hardware.memorySizeMb", "4096")
    }
})