The landscape of continuous integration and continuous delivery has undergone a paradigm shift in recent years, moving away from monolithic, physical server installations toward containerized, ephemeral, and highly scalable architectures. At the forefront of this transformation is JetBrains TeamCity, a powerful CI/CD server that has increasingly embraced Docker as a first-class citizen for both its own deployment and its build execution environments. The integration of TeamCity with Docker is not merely a matter of convenience; it is a strategic imperative for modern software engineering teams seeking to streamline their development pipelines, ensure environment consistency, and reduce the operational overhead associated with maintaining complex build infrastructure. By encapsulating the TeamCity server and its associated build agents within Docker containers, organizations can achieve unprecedented levels of isolation, reproducibility, and portability. This approach simplifies the initial setup, facilitates seamless upgrades, and allows for the rapid provisioning of build resources that are tailored to specific project requirements. The ability to run a fully functional TeamCity server and a Docker-capable agent using a simple Docker Compose configuration represents a significant reduction in barrier to entry for teams looking to implement robust CI/CD practices. However, beneath the simplicity of the setup lies a complex web of technical considerations, security implications, and architectural decisions that must be carefully navigated to ensure a stable and secure production environment.
The core value proposition of running TeamCity in Docker lies in the decoupling of the application logic from the underlying host operating system. This decoupling ensures that the CI/CD server behaves identically regardless of whether it is deployed on a local developer machine, a cloud-based virtual machine, or a bare-metal server in a data center. Furthermore, the use of Docker for build agents enables a level of flexibility that was previously difficult to achieve with traditional agent installations. By mounting the host Docker socket into the agent container, the agent can leverage the host’s Docker daemon to build images, a technique known as "Docker-in-Docker" or DinD, albeit with specific security and isolation caveats. This capability is critical for modern microservices architectures where each service is packaged as a Docker image, and the CI/CD pipeline must be capable of building, testing, and pushing these images to a registry. The following analysis provides an exhaustive exploration of the technical mechanics, configuration nuances, security best practices, and advanced build strategies associated with running TeamCity with Docker, drawing upon official documentation, community best practices, and detailed technical specifications.
Foundational Infrastructure and Directory Structure
Before initiating the deployment of a TeamCity server and agent within a Docker environment, it is imperative to establish a robust foundational infrastructure. The integrity of the CI/CD pipeline depends heavily on the proper management of persistent data, logs, and configuration files. A common pitfall in containerized deployments is the loss of data upon container termination, as Docker containers are inherently ephemeral. To mitigate this risk, a structured approach to directory management is required. The recommended practice is to separate server data, logs, and agent configuration into distinct directories. This separation of concerns ensures that critical build history, configuration settings, and diagnostic logs are preserved across container restarts, upgrades, and migrations.
The initial setup involves creating a dedicated root directory for the TeamCity deployment. Within this root directory, three subdirectories must be established: data, logs, and agent. The data directory serves as the primary repository for the TeamCity server’s persistent data, including build histories, configuration settings, and database files if an internal database is used. The logs directory is designated for storing server logs, which are critical for troubleshooting issues, monitoring system health, and auditing build activities. The agent directory is reserved for the agent’s configuration files, ensuring that the agent can maintain its state and connection parameters independently of the server.
To create this structure, the following terminal commands should be executed:
bash
mkdir -p teamcity/{data,logs,agent}
cd teamcity
The resulting directory structure should resemble the following layout:
text
teamcity/
├── docker-compose.yml
├── data/
├── logs/
└── agent/
This directory structure forms the backbone of the deployment, providing the necessary anchors for volume mounts within the Docker Compose configuration. The docker-compose.yml file will reference these directories, mapping them to specific paths within the Docker containers. This mapping ensures that the ephemeral nature of the containers does not compromise the persistence of critical CI/CD data. The data directory is particularly important, as it contains the heart of the TeamCity server. Loss of this data would result in the loss of all project configurations, build histories, and user accounts, effectively resetting the CI/CD environment to a blank slate. Therefore, it is essential that this directory resides on fast, persistent storage to ensure optimal performance and data durability.
Docker Compose Configuration and Service Orchestration
The heart of the Dockerized TeamCity deployment is the docker-compose.yml file. This file defines the services that comprise the CI/CD environment, their configurations, and their interdependencies. A well-crafted docker-compose.yml file ensures that the TeamCity server and agent are deployed consistently, with the correct versions, ports, and volume mounts. The use of Docker Compose simplifies the orchestration of these services, allowing them to be started, stopped, and managed as a single unit.
The following configuration represents a robust starting point for a TeamCity server and a Docker-capable agent:
```yaml
version: "3.8"
services:
teamcity-server:
image: jetbrains/teamcity-server:2023.11.4
containername: teamcity-server
ports:
- "8111:8111"
volumes:
- ./data:/data/teamcityserver/datadir
- ./logs:/opt/teamcity/logs
restart: unless-stopped
teamcity-agent:
image: jetbrains/teamcity-agent:2023.11.4
containername: teamcity-agent
user: root
environment:
- SERVERURL=http://teamcity-server:8111
volumes:
- ./agent:/data/teamcityagent/conf
- /var/run/docker.sock:/var/run/docker.sock
dependson:
- teamcity-server
restart: unless-stopped
```
This configuration defines two services: teamcity-server and teamcity-agent. The teamcity-server service uses the official JetBrains TeamCity server image, pinned to a specific version to ensure reproducibility. The container name is explicitly set to teamcity-server for easy identification. The server exposes port 8111, which is the default port for TeamCity web interfaces, mapping it to the host machine’s port 8111. The volume mounts for the server are critical: ./data is mounted to /data/teamcity_server/datadir within the container, and ./logs is mounted to /opt/teamcity/logs. This ensures that all server data and logs are persisted on the host machine. The restart: unless-stopped policy ensures that the server container automatically restarts if it crashes or if the Docker daemon restarts, enhancing the availability of the CI/CD service.
The teamcity-agent service is configured to work in tandem with the server. It uses the official JetBrains TeamCity agent image, also pinned to the same version as the server to ensure compatibility. The agent runs as the root user, a necessary configuration to grant the agent the permissions required to interact with the host Docker daemon. The SERVER_URL environment variable is set to http://teamcity-server:8111, directing the agent to the server’s internal Docker network address. This ensures that the agent can discover and connect to the server without needing to know the host machine’s IP address. The agent’s configuration directory, ./agent, is mounted to /data/teamcity_agent/conf within the container, preserving the agent’s state. Crucially, the host’s Docker socket, /var/run/docker.sock, is mounted into the agent container at the same path. This mount is what enables the agent to execute Docker commands on the host machine, allowing it to build Docker images. The depends_on directive ensures that the agent container only starts after the server container is up, facilitating a smooth initialization process.
Image Versioning and Platform Support
The selection of the correct Docker image version and platform is a critical aspect of TeamCity deployment. JetBrains provides official Docker images for both the TeamCity server and agent, which are available on Docker Hub. These images are regularly updated to reflect new releases of TeamCity, ensuring that users have access to the latest features and security patches. The Docker Hub repository for jetbrains/teamcity-server lists a variety of tags, including latest, specific version tags such as 2025.11.4 and 2025.11.3, and platform-specific tags.
The latest tag points to the most recent release, which may change frequently. For production environments, it is generally advisable to pin the image to a specific version, such as 2025.11.4, to avoid unexpected breaking changes during automatic updates. The 2025.11.4 tag, for instance, includes support for multiple platforms, including linux/amd64, linux/arm64/v8, and Windows variants such as windows/amd64 and nanoserver editions for Windows Server 2019 and 2022. The image sizes vary significantly depending on the platform; the Linux AMD64 image is approximately 1.68 GB, while the Windows AMD64 image is significantly larger at 4.43 GB. This disparity reflects the different runtime requirements and base images used for each platform.
For users building custom images or requiring specific configurations, JetBrains provides a GitHub repository, JetBrains/teamcity-docker-images, which contains the source code and build scripts for the official Docker images. This repository allows users to inspect the image contents, customize the build process, and generate their own images if necessary. The repository includes tools used within the release process, providing transparency into how the official images are constructed. This level of openness is beneficial for security-conscious organizations that may wish to audit the image contents or add custom configurations.
Security Considerations and Socket Mounting
One of the most significant security considerations in a Dockerized TeamCity deployment is the mounting of the Docker socket. By mounting /var/run/docker.sock into the agent container, the agent gains full control over the host Docker daemon. This means that the agent can run any Docker command, including creating new containers, mounting host directories, and potentially executing arbitrary code with root privileges on the host machine. While this capability is essential for building Docker images, it also introduces a substantial security risk. If an attacker gains access to the TeamCity agent or injects malicious build steps, they could exploit the socket mount to compromise the host system.
To mitigate this risk, several best practices should be followed. First, access to the TeamCity server and the Docker socket should be strictly limited. This can be achieved by using firewalls to restrict access to the TeamCity web interface and by ensuring that the Docker socket is not exposed to untrusted networks. Second, the agent should be configured to run with the minimum necessary privileges. In the provided configuration, the agent runs as root, which is required for socket access, but efforts should be made to limit the agent’s capabilities within the container. Third, for production environments, it is recommended to consider alternative isolation strategies, such as Docker-in-Docker (DinD) setups or remote Docker hosts. DinD involves running a separate Docker daemon inside the agent container, which provides better isolation than mounting the host socket. However, DinD can be more complex to configure and may have performance implications. Remote Docker hosts involve running the agent on a separate machine with its own Docker daemon, further separating the build environment from the server environment.
Additionally, users should be aware that running the agent as root gives it full control over Docker on the host. This is a known limitation of the socket mount approach and should be carefully evaluated in the context of the organization’s security policies. Regular backups of the TeamCity server data are also essential to protect against data loss in the event of a security breach or system failure.
Initial Setup and Web Interface Configuration
Once the Docker containers are started using the command docker compose up -d, the TeamCity server becomes accessible via the web interface at http://localhost:8111. The -d flag runs the containers in the background, allowing the user to continue working in the terminal. Upon first access, the TeamCity web setup wizard is launched, guiding the user through the initial configuration process.
The wizard begins by confirming the data directory, which is mapped to the ./data directory on the host machine. This step ensures that the server knows where to store its persistent data. Next, the user is prompted to choose a database. For testing and development environments, the internal database is a convenient option, as it requires no additional configuration. For production environments, however, an external database such as PostgreSQL or MySQL is recommended for better performance, scalability, and data integrity. After selecting the database, the user must accept the TeamCity license agreement and create an initial admin account. This account will have full administrative privileges over the TeamCity server, allowing the user to configure projects, build configurations, and agents.
After the server setup is complete, the user must authorize the agent. The agent container, which was started as part of the Docker Compose configuration, will automatically attempt to connect to the server. However, it will not be authorized to run builds until the administrator explicitly approves it. To authorize the agent, the user must navigate to the "Agents" section in the TeamCity UI and select the "Unauthorized" tab. Here, the agent will be listed with its configuration details. The user should click the "Authorize" button to grant the agent permission to connect to the server. Once authorized, the agent will show as "Connected" and "Compatible" in the UI, indicating that it is ready to execute build steps.
Verifying Docker Capability and Build Execution
With the agent authorized and connected, the next step is to verify that it can successfully build Docker images. This verification is crucial because the primary benefit of this setup is the agent’s ability to interact with the host Docker daemon. To verify the Docker capability, the user can open a terminal inside the agent container and execute the docker version command:
bash
docker exec -it teamcity-agent docker version
This command will display the Docker client and server versions, confirming that the agent has access to the Docker daemon. If the command returns successfully, it indicates that the socket mount is working correctly and the agent can execute Docker commands.
Once the Docker capability is verified, the user can configure build steps in TeamCity to build and push Docker images. TeamCity supports various build step types, including "Command Line" and "Docker runner". For simple Docker builds, the "Command Line" step can be used to execute standard Docker commands, such as:
bash
docker build -t my-app:%build.number% .
docker push my-app:%build.number%
In this example, %build.number% is a TeamCity macro that is replaced with the current build number, ensuring that each build produces a uniquely tagged image. The docker push command then uploads the image to a Docker registry, such as Docker Hub or a private registry.
For more complex builds, the "Docker runner" build step provides a more structured interface. The user can specify the Dockerfile path, the image name, and additional arguments. TeamCity will then use the agent’s Docker access to build the image according to the specified parameters. This approach simplifies the configuration of Docker builds and reduces the likelihood of errors.
Advanced Build Strategies: Multi-Architecture Images
As software applications increasingly need to support multiple hardware architectures, the ability to build multi-architecture Docker images has become a critical requirement. TeamCity supports this capability through the use of Docker Buildx, a build toolkit that extends the Docker build command. Buildx allows users to build images for multiple platforms in a single build step, streamlining the process of creating multi-arch images.
To build multi-architecture images in TeamCity, the user can use the "other..." command option in the Docker runner build step. This option allows the user to execute any custom Docker command, including Buildx commands. For example, to create a new Buildx builder instance, the user can add a new Docker runner build step, switch the Docker command option to "other...", and specify the following details:
- Command name:
buildx - Additional arguments for the command:
create --use
TeamCity will combine these fields into a single command: docker buildx create --use. This command creates a new builder instance and sets it as the active builder. The user can then repeat this process for subsequent commands, such as building the image for multiple platforms:
bash
docker buildx build <path> --platform linux/amd64,linux/arm64
This command builds the image for both AMD64 and ARM64 platforms, producing a manifest that allows Docker to pull the correct image variant for the target architecture. This capability is essential for deploying applications to diverse environments, such as cloud instances, edge devices, and mobile devices.
Custom Image Generation and Repository Management
While JetBrains provides official Docker images for TeamCity, some organizations may have specific requirements that necessitate the use of custom images. For instance, an organization may need to include additional tools, libraries, or security configurations in the TeamCity agent image. In such cases, the official Docker images can be used as a base, and custom images can be generated using the source code provided in the JetBrains/teamcity-docker-images GitHub repository.
To generate a custom image, the user must first ensure that Docker is installed on their local machine. Then, they should clone the teamcity-docker-images repository and download the required TeamCity archive file. For example, to download the TeamCity 2021.2 archive, the user can execute:
bash
wget -c https://download.jetbrains.com/teamcity/TeamCity-2021.2.tar.gz -O - | tar -xz -C context
This command downloads the archive and extracts it into the context directory within the cloned repository. The user must then unpack the TeamCity files into the context/TeamCity directory. Finally, the user can generate the custom image using the corresponding build script in the context/generated directory. These scripts are provided in both .cmd and .sh formats, catering to different operating systems.
Custom images offer a high degree of flexibility, allowing organizations to tailor their CI/CD environment to their specific needs. However, they also require ongoing maintenance to ensure that they remain compatible with new versions of TeamCity and Docker. Therefore, organizations should carefully weigh the benefits of customization against the costs of maintenance.
Operational Management and Troubleshooting
Managing a Dockerized TeamCity environment involves regular operational tasks, such as stopping, restarting, and updating the containers. To stop the TeamCity server and agent, the user can execute:
bash
docker compose down
This command stops and removes the containers, but it preserves the volumes, ensuring that data is not lost. To restart the services, the user can execute:
bash
docker compose up -d
This command starts the containers in the background, and the agent will automatically reconnect to the server. This ease of management is one of the key advantages of using Docker for TeamCity deployment.
In the event of issues, the logs stored in the ./logs directory provide valuable diagnostic information. The user can examine these logs to identify errors, performance bottlenecks, or security incidents. Additionally, the TeamCity web interface provides a rich set of monitoring and reporting tools, allowing the user to track build success rates, agent utilization, and system health.
For more advanced troubleshooting, the user can access the agent’s log file, which is stored in the ./agent directory. This log contains detailed information about the agent’s interactions with the server and the Docker daemon, helping to pinpoint issues related to connectivity, configuration, or build execution.
Sudo Configuration and Privilege Escalation
In some environments, Docker commands may require elevated privileges to execute successfully. TeamCity provides a mechanism to enforce the use of sudo for Docker commands on the agent. This can be enabled by adding the teamcity.docker.use.sudo=true setting in the build agent configuration file or as an agent’s system property. When this setting is enabled, the TeamCity agent will prefix all Docker commands with sudo, ensuring that they are executed with root privileges.
However, using sudo requires careful configuration of the sudoers file to avoid common issues, such as the requiretty restriction. To configure the sudoers file, the user should use the visudo command:
bash
visudo
It is recommended to remove or comment out the Defaults requiretty line from the sudoers file to prevent problems with Docker login commands. This line requires that sudo commands be executed from a terminal, which can cause issues in non-interactive environments such as CI/CD pipelines. By removing this restriction, the agent can execute Docker commands without encountering tty-related errors.
Conclusion
The integration of JetBrains TeamCity with Docker represents a significant advancement in the field of continuous integration and continuous delivery. By leveraging Docker for both the server and the build agents, organizations can achieve a high degree of flexibility, scalability, and reproducibility in their CI/CD pipelines. The ability to run a fully functional TeamCity server and a Docker-capable agent using a simple Docker Compose configuration lowers the barrier to entry for teams looking to implement robust CI/CD practices. However, this convenience comes with important security and operational considerations. The mounting of the Docker socket into the agent container provides the necessary capability to build Docker images but also introduces significant security risks that must be carefully managed. Furthermore, the choice between using official images, custom images, and different isolation strategies such as Docker-in-Docker or remote hosts depends on the specific needs and constraints of the organization. By understanding the technical details of the Docker Compose configuration, the image versioning and platform support, the security implications of socket mounting, and the advanced build strategies for multi-architecture images, teams can design and maintain a robust, secure, and efficient CI/CD environment that meets the demands of modern software development. The continuous evolution of TeamCity and Docker, supported by active community contributions and official documentation, ensures that this integration will remain a powerful tool for software engineers in the years to come.