In part one of this series we are going to explore a CI/CD option you may not be familiar with but should definitely be on your radar. I used Jetbrains TeamCity for several months at my last company and really enjoyed my time with it. A couple of the things I like most about it are:
- Ability to declare global variables and have them be passed down to all projects
- Ability to declare variables that are made up of other variables
I like to use private or self hosted Docker registries for a lot of my projects and one of the pain points I have had with some other solutions (well mostly Bitbucket) is that they don’t integrate well with these private registries and when I run into a situation where I am pushing an image to or pulling an image from a private registry it get’s a little messy. TeamCity is nice in that I can add a connection to my private registry in my root project and them simply add that as a build feature to any projects that may need it. Essentially, now I only have one place where I have to keep those credentials and manage that connection.
Another reason I love it is the fact that you can create really powerful build templates that you can reuse. This became very powerful when we were trying to standardize our build processes. For example, most of the apps we build are .NET
backends and React
frontends. We built docker images for every project and pushed them to our private registry. TeamCity gave us the ability to standardize the naming convention and really streamline the build process. Enough about that though, the rest of this series will assume that you are using TeamCity. This post will focus on getting up and running using Ansible.
Installation and Setup
For this I will assume that you already have Ansible on your machine and that you will be installing TeamCity locally. You can simply follow along with the installation guide here. We will be creating an Ansible playbook based on the following steps. If you just want the finished code, you can find it on my Gitea instance here:
Step 1 : Create project and initial playbook
To get started go ahead and create a new directory to hold our configuration:
mkdir ~/projects/teamcity-configuration-ansible
touch install-teamcity-server.yml
Now open up install-teamcity-server.yml
and add a task to install Java 17 as it is a prerequisite. You will need sudo for this task. ***As of this writing TeamCity does not support Java 18 or 19. If you try to install one of these you will get an error when trying to start TeamCity.
---
- name: Install Teamcity
hosts: localhost
become: true
become_user: sudo
# Add some variables to make our lives easier
vars:
java_version: "17"
teamcity:
installation_path: /opt/TeamCity
version: "2023.11.4"
tasks:
- name: Install Java
ansible.builtin.apt:
name: openjdk-{{ java_version }}-jre-headless
update_cache: yes
state: latest
install_recommends: no
The next step is to create a dedicated user account. Add the following task to install-teamcity-server.yml
- name: Add Teamcity User
ansible.builtin.user:
name: teamcity
Next we will need to download the latest version of TeamCity. 2023.11.4 is the latest as of this writing. Add the following task to your install-teamcity-server.yml
- name: Download TeamCity Server
ansible.builtin.get_url:
url: https://download.jetbrains.com/teamcity/TeamCity-{{teamcity.version}}.tar.gz
dest: /opt/TeamCity-{{teamcity.version}}.tar.gz
mode: '0770'
Now to install TeamCity Server add the following:
- name: Install TeamCity Server
ansible.builtin.shell: |
tar xfz /opt/TeamCity-{{teamcity.version}}.tar.gz
rm -rf /opt/TeamCity-{{teamcity.version}}.tar.gz
args:
chdir: /opt
Now that we have everything set up and installed we want to make sure that our new teamcity
user has access to everything they need to get up and running. We will add the following lines:
- name: Update permissions
ansible.builtin.shell: chown -R teamcity:teamcity /opt/TeamCity
This gives us a pretty nice setup. We have TeamCity server installed with a dedicated user account. The last thing we will do is create a systemd
service so that we can easily start/stop the server. For this we will need to add a few things.
- A service file that tells our system how to manage TeamCity
- A j2 template file that is used to create this service file
- A handler that tells the system to run
systemctl daemon-reload
once the service has been installed.
Go ahead and create a new templates folder with the following teamcity.service.j2
file
[Unit]
Description=JetBrains TeamCity
Requires=network.target
After=syslog.target network.target
[Service]
Type=forking
ExecStart={{teamcity.installation_path}}/bin/runAll.sh start
ExecStop={{teamcity.installation_path}}/bin/runAll.sh stop
User=teamcity
PIDFile={{teamcity.installation_path}}/teamcity.pid
Environment="TEAMCITY_PID_FILE_PATH={{teamcity.installation_path}}/teamcity.pid"
[Install]
WantedBy=multi-user.target
Your project should now look like the following:
$: ~/projects/teamcity-ansible-terraform
.
├── install-teamcity-server.yml
└── templates
└── teamcity.service.j2
1 directory, 2 files
That’s it! Now you should have a fully automated installed of TeamCity Server ready to be deployed wherever you need it. Here is the final playbook file, also you can find the most up to date version in my repo:
---
- name: Install Teamcity
hosts: localhost
become: true
become_method: sudo
vars:
java_version: "17"
teamcity:
installation_path: /opt/TeamCity
version: "2023.11.4"
tasks:
- name: Install Java
ansible.builtin.apt:
name: openjdk-{{ java_version }}-jdk # This is important because TeamCity will fail to start if we try to use 18 or 19
update_cache: yes
state: latest
install_recommends: no
- name: Add TeamCity User
ansible.builtin.user:
name: teamcity
- name: Download TeamCity Server
ansible.builtin.get_url:
url: https://download.jetbrains.com/teamcity/TeamCity-{{teamcity.version}}.tar.gz
dest: /opt/TeamCity-{{teamcity.version}}.tar.gz
mode: '0770'
- name: Install TeamCity Server
ansible.builtin.shell: |
tar xfz /opt/TeamCity-{{teamcity.version}}.tar.gz
rm -rf /opt/TeamCity-{{teamcity.version}}.tar.gz
args:
chdir: /opt
- name: Update permissions
ansible.builtin.shell: chown -R teamcity:teamcity /opt/TeamCity
- name: TeamCity | Create environment file
template: src=teamcity.service.j2 dest=/etc/systemd/system/teamcityserver.service
notify:
- reload systemctl
- name: TeamCity | Start teamcity
service: name=teamcityserver.service state=started enabled=yes
# Trigger a reload of systemctl after the service file has been created.
handlers:
- name: reload systemctl
command: systemctl daemon-reload