Full pipeline design, matrix builds, conditional steps, clean-up policies, server backup and restore, JVM tuning, Docker agents, Kotlin DSL versioned settings, and the upgrade procedure
A production-ready Java microservice pipeline integrating everything from Phases 1–14:
main branch — feature branches build and test but don't produce artifactsTeamCity doesn't have a native matrix syntax like GitHub Actions, but you can achieve matrix builds by creating one build configuration per combination and grouping them under a composite.
# Create one build config per JDK version using templates:
# Template: Maven-Test-Template
# Parameter: jdk.version (required)
# Agent requirement: java.version starts with %jdk.version%
# Maven goals: test
# Artifact: target/surefire-reports/**/* => test-reports/%jdk.version%
# Apply template to create:
# Test-JDK8 → jdk.version=1.8
# Test-JDK11 → jdk.version=11
# Test-JDK17 → jdk.version=17
# Composite: All-Java-Tests
# Snapshot deps on: Compile, Test-JDK8, Test-JDK11, Test-JDK17
# Similar approach with agent requirements on OS:
# Test-Linux → agent requirement: teamcity.agent.os.name=Linux
# Test-Windows → agent requirement: teamcity.agent.os.name=Windows
TeamCity 2017.2 doesn't have native "if/else" step conditions based on parameter values. Use these patterns instead:
#!/bin/bash
# Only deploy if on main branch
if [ "%teamcity.build.branch%" = "refs/heads/main" ]; then
echo "Main branch — deploying..."
./deploy.sh --env=staging
else
echo "Feature branch — skipping deploy"
fi
# Instead of conditional steps, use build chain:
# BuildAndTest → runs on all branches
# Deploy → runs only on main (trigger with branch filter: +refs/heads/main)
# VCS trigger on Deploy config:
# Branch filter: +refs/heads/main (only triggers on main)
#!/bin/bash
# Check if skip flag is set
if [ "%run.integration.tests%" = "false" ]; then
echo "##teamcity[buildStatus status='SUCCESS' text='Integration tests skipped']"
exit 0
fi
# Run integration tests
mvn verify -P integration
Without clean-up policies, the TeamCity data directory grows indefinitely. Set policies at server and project levels.
Administration → Server Administration → Clean-up Settings:
Administration → <Project> → Clean-up Rules → Add rule:
| Keep | What is deleted |
|---|---|
| Last N builds | Everything older than the N most recent builds |
| Builds for last N days | Builds older than N days |
| Last N successful builds | Failed builds beyond the last N successes |
# Recommended rules for an active service build config:
# Keep: last 30 builds (full history including logs + artifacts)
# Keep: last 100 builds (history only, no artifacts)
# Keep: pinned builds indefinitely (don't delete release builds)
# This keeps recent builds with artifacts for debugging,
# and longer history for trend analysis, without infinite disk growth.
Pinning a build exempts it from all clean-up rules. If your release process auto-pins every successful build, you'll accumulate hundreds of pinned builds and clean-up will never free any disk space. Only pin builds that represent actual releases — unpin test/staging builds after validation.
<Data Dir>/backup/.TeamCity can create backups on a schedule. Enable in Administration → Backup → Enable scheduled backups. Set time and what to include. Copy backup ZIPs to off-server storage (S3, NFS).
# 1. Stop the TeamCity server
systemctl stop teamcity
# 2. Run maintainDB restore tool
cd /opt/teamcity/bin
./maintainDB.sh restore \
-F /var/lib/teamcity/backup/TeamCity_Backup_20260612_0200.zip \
-T /var/lib/teamcity # data directory
# 3. Start the server
systemctl start teamcity
# The maintainDB tool restores: database schema, data, and config files
Daily database + config backup (no build logs/artifacts — too large). Weekly full backup (database + config + artifacts). Keep 30 days of daily backups and 6 months of weekly backups on off-server storage. Test your restore procedure quarterly — a backup you've never restored is an untested backup.
# TeamCity server JVM settings
# Linux: /opt/teamcity/bin/teamcity-server.sh
# Windows: set TEAMCITY_SERVER_OPTS in registry or service wrapper
export TEAMCITY_SERVER_MEM_OPTS="\
-Xms1g \
-Xmx8g \
-XX:MaxMetaspaceSize=512m \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:G1HeapRegionSize=16m \
-XX:InitiatingHeapOccupancyPercent=45"
# TeamCity agent JVM settings
# conf/buildAgent.properties:
# JAVA_OPTS=-Xmx512m
# Monitoring GC:
# Add to server opts: -Xloggc:/var/log/teamcity-gc.log -XX:+PrintGCDateStamps
| Team size | Recommended -Xmx | Notes |
|---|---|---|
| 1–5 developers, <20 builds/day | 2g | Default 750m is too small even for dev |
| 5–20 developers, <100 builds/day | 4g | Most small teams |
| 20–100 developers, <500 builds/day | 8g | Medium teams |
| 100+ developers, 500+ builds/day | 16g+ | Consider separate artifact storage |
Run TeamCity agents inside Docker containers for isolated, disposable build environments.
# Pull official TeamCity agent image
docker pull jetbrains/teamcity-agent:2017.2.2
# Run a TeamCity agent container
docker run -d \
--name tc-agent-1 \
-e SERVER_URL="http://YOUR_TC_SERVER:8111" \
-e AGENT_NAME="Docker-Agent-01" \
-v tc-agent-1-work:/opt/buildagent/work \
-v tc-agent-1-system:/opt/buildagent/system \
jetbrains/teamcity-agent:2017.2.2
# Mount the host Docker socket for Docker builds inside the agent
docker run -d \
--name tc-agent-docker \
-e SERVER_URL="http://YOUR_TC_SERVER:8111" \
-v /var/run/docker.sock:/var/run/docker.sock \
-v tc-agent-docker-work:/opt/buildagent/work \
--privileged \
jetbrains/teamcity-agent:2017.2.2
Use the agent image tag that matches your server version: jetbrains/teamcity-agent:2017.2.2. Using latest may pull a newer agent that is incompatible with an older server. Pin the image tag and update it deliberately when upgrading the server.
A complete Kotlin DSL example for a project with one build configuration, demonstrating the key DSL elements:
// .teamcity/settings.kts
import jetbrains.buildServer.configs.kotlin.v2019_2.*
import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.commitStatusPublisher
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.maven
import jetbrains.buildServer.configs.kotlin.v2019_2.triggers.vcs
import jetbrains.buildServer.configs.kotlin.v2019_2.vcs.GitVcsRoot
version = "2017.2"
project {
vcsRoot(MyGitHubRepo)
buildType(Build)
}
object MyGitHubRepo : GitVcsRoot({
name = "GitHub — myorg/user-service"
url = "https://github.com/myorg/user-service.git"
branch = "refs/heads/main"
branchSpec = "+:refs/heads/*\n+:refs/pull/*/head"
authMethod = password {
userName = "myorg-ci-bot"
password = "credentialsJSON:token-id" // stored in TeamCity credentials store
}
})
object Build : BuildType({
name = "Build"
vcs { root(MyGitHubRepo) }
artifactRules = "target/user-service-*.jar => artifacts"
steps {
maven {
name = "Compile and Test"
goals = "clean verify"
mavenVersion = defaultProvidedVersion()
jvmArgs = "-Xmx512m"
}
}
triggers {
vcs {
branchFilter = "+:refs/heads/*\n+:refs/pull/*/head"
quietPeriodMode = USE_DEFAULT
}
}
features {
commitStatusPublisher {
publisher = github {
githubUrl = "https://api.github.com"
authType = personalToken {
token = "credentialsJSON:github-status-token"
}
}
}
}
failureConditions {
executionTimeoutMin = 30
testFailure(true)
}
})
In-place upgrade procedure for TeamCity 2017.2.2 to a newer version:
systemctl stop teamcitysystemctl start teamcityIf upgrading more than 2–3 major versions (e.g., 2017.2 → 2022.10), upgrade in steps. Skipping too many versions can cause database migration failures because migration scripts are cumulative. Recommended: upgrade one major version at a time, verify, then continue.
TeamCity 2017.2 does not support multi-server active-active clustering. A single server is the only supported architecture. For high availability:
Combining everything in this guide into a complete production build configuration set for a Java Spring Boot microservice: