Creating a Docker Stack for Atlassian Jira, Confluence and Bitbucket

by 20th September 2021Atlassian, Development, Docker, Software, Technological Thoughts

I have been using Atlassian products for quite a while now, both with clients and my own company.

Jira is an incredibly effective tool to aid a delivery lifecycle, and although it’s geared towards scrum and kanban agile methodologies, it can cater for so much more.

Confluence is a fantastic repository, allowing you to build informational and collaborative webpages and store documentation. It’s far more intuitive, less memory intensive, and more user friendly than Sharepoint.

Bitbucket is a graphical layer over git, and again is very straightforward for managing data and code repositories.

In the past, I have deployed these tools directly to Linux servers, and that hasn’t been so straightforward. Any updates to the core functionality was often fraught with issues, even if you built a script particular to your configuration to make sure the upgrade was successful.

Enter Docker. By using a Docker Compose script, you can deploy all three tools and a MySQL instance to support them. There is additional configuration that needs to be carried out for each tool, but rather than repeat documentation, I recommend that you read the help pages for each one.

Atlassian on Docker Documentation

I am assuming that you know enough about Atlassian products and Docker to do the basic configuration – this article simply explains how you can use a Docker Compose script to maintain them and include a healthcheck for each instance.

Running the Docker Compose File

If you just want to go ahead and run the Docker Compose file (using the command line, or something handy like Portainer) then you can copy the full script below.

However it is worth reading through the sections so you have a full understanding of the configuration, and the minimal tweaks that have been done to allow it to work with MySQL.

Environment Settings

Whilst similar values are used between Bitbucket, Confluence and Jira, for some reason the variables used by Bitbucket are different!

For Bitbucket, the JDBC settings specify the MySQL user and password to connect to the database, with the URL specifying the server, port, database name and parameters required for connection. For Confluence and Jira, these are specified as ATL_JDBC settings. I have used the particularly imaginative user name of “atlassian”, which you can of course change, but you do need to set your own password.

 In all three cases, I have the servers running behind a reverse proxy so that I can connect to them using a domain name and SSL. So the SERVER_PROXY or ATL_PROXY settings define your domain, proxy port and whether you are using https or not (you should be!).

Confluence and Jira have some additional settings to specify the database type and which driver to use for MySQL. On top of that, they also specify the internal port used to connect to the application (typically 8090 for Confluence and 8080 for Jira).

Bitbucket Environment Settings

- JDBC_PASSWORD={password}
- JDBC_URL=jdbc:mysql://localhost:3306/bitbucketdb?useUnicode=true&characterEncoding=UTF8&sessionVariables=default_storage_engine=InnoDB&autoReconnect=true&useSSL=false
- JDBC_USER=atlassian
- SERVER_PROXY_NAME=bitbucket.yourdomain.com
- SERVER_PROXY_PORT=443
- SERVER_SCHEME=https
- SERVER_SECURE=true

Confluence and Jira Environment Settings

- ATL_DB_DRIVER=com.mysql.jdbc.Driver
- ATL_DB_TYPE=mysql
- ATL_JDBC_PASSWORD={password}
- ATL_JDBC_URL=jdbc:mysql://localhost:3306/confluencedb?useUnicode=true&autoReconnect=true&useSSL=false
- ATL_JDBC_USER=atlassian
- ATL_PROXY_NAME=confluence.yourdomain.com
- ATL_PROXY_PORT=443
- ATL_TOMCAT_PORT=8090
- ATL_TOMCAT_SCHEME=https
- ATL_TOMCAT_SECURE=true

The depends_on Parameter

The depends_on parameter is really quite simple – it tells Docker Compose that the container that is being created (Bitbucket, Confluence or Jira) needs MySQL and if that isn’t available, the container can’t be created.

This doesn’t check to see that MySQL is up and running successfully however: just that the MySQL container exists. To perform extra validation, you need to run an external script, something that is beyond the scope of this article. Prior to version 3 of Docker Compose, you were able to use the term condition, but that has since been deprecated.
Docker themselves do explain how it can be done.

- mysql

Atlassian Healthchecks

test: 'curl localhost:7990/status | grep -q RUNNING'
interval: 1m
start_period: 10m
timeout: 10s

I have added a section in to the Docker Compose file for each container which periodically checks to see if the container is up and running.

All Atlassian tools have an API that can be called which returns the status of the service, so by adding in a test which calls the API and then checks to see if the response is “RUNNING”, we can determine if the container is healthy or not.

The interval parameter tells Docker how often to carry out the check. For me, one minute is more than enough.

The start_period parameter tells Docker to ignore the health check for the given period whilst the container starts up. As Atlassian tools can take a while to load up, I have set this to 10 minutes.

The last parameter, timeout, tells Docker when to mark the container as unhealthy if it gets no response from the test.

MySQL Healthcheck

test: '/usr/bin/mysql --user=healthcheck --password=healthcheck --execute "SHOW DATABASES;"'
interval: 1m
start_period: 1m
timeout: 10s
The MySQL parameters are the same but, as MySQL starts up a lot more quickly, I have set the start_period to one minute.

For the test parameter, I am calling the mysql command and logging in with a user called “healthcheck”. That user then simply runs a SHOW DATABASES SQL command against the instance and if it gets a response, then the container is up and running.

The last thing you want is to include a user and password on a command line that has significant amounts of access to the database. So, once the MySQL database is up and running, you will want to run the following command, to just allow the “healthcheck” user to run the SHOW DATABASES command and nothing else.

Granting Permission for the Healthcheck User in MySQL

CREATE USER 'healthcheck'@'localhost' IDENTIFIED BY 'healthcheck';
GRANT SHOW DATABASES  ON *.* TO 'healthcheck'@'localhost';
FLUSH PRIVILEGES;
- /docker/bitbucket:/var/atlassian/application-data/bitbucket:rw
- /docker/configuration/mysql-connector-java-5.1.48-bin.jar:/var/atlassian/application-data/bitbucket/lib/mysql-connector-java-5.1.48-bin.jar:rw

Bitbucket Volumes

The Bitbucket volumes are straightforward: The first configures an external directory hosted on your client Linux server (in this case, found at /docker/bitbucket) to be used for any Bitbucket data files. This ensures that whenever you update the Bitbucket container, your data is not lost.

The second references the MySQL Java connector that is needed by Bitbucket to connect to MySQL. The version that can be used by Bitbucket is 5.1.* unlike Confluence and Jira, which can use newer versions.

Confluence and Jira Volumes

The first two volumes for Confluence and Jira are similar to Bitbucket, in that they point to the external directory for your data and the MySQL Java Connector respectively. I’ve pointed Confluence and Jira to a newer version of the connector than Bitbucket, but they will happily all run on a 5.1.* version.

The last volume for Confluence and Jira is to store the log files, which can be handy if you need to dig deeper into any issues that happen with the Atlassian tools.

 

- /docker/confluence:/var/atlassian/application-data/confluence:rw
- /docker/configuration/mysql-connector-java-8.0.21.jar:/opt/atlassian/confluence/confluence/WEB-INF/lib/mysql-connector-java-8.0.21.jar:rw
- /docker/confluence/logs:/opt/atlassian/confluence/logs:rw

MySQL Volumes

For MySQL, we are again creating a volume, in this case /docker/mysql, to store the MySQL data and databases.

The second parameter is one I have added for the mysql.cnf, which stores all the configuration values needed to allow the Atlassian tools to interoperate with MySQL.I found it convenient to keep this, along with the MySQL Java connectors in one place, so that they could easily be modified or updated if necessary.

 

- /docker/mysql:/var/lib/mysql:rw
- /docker/configuration/conf.d/mysql.cnf:/etc/mysql/conf.d/my.cnf:rw

MySQL mysql.cnf File for Atlassian

[mysqld]
# MySQL used for Atlassian services

# Atlassian Service Requirements
binlog_format=row
character_set_server=utf8mb4
collation_server=utf8mb4_bin
default_storage_engine=INNODB
innodb_log_file_size=2G
log-bin-trust-function-creators=1
max_allowed_packet=256M
transaction_isolation=READ-COMMITTED

# InnoDB Configuration
innodb_default_row_format=DYNAMIC
innodb_file_per_table=ON
lower_case_table_names=0

MySQL Root Password

The first time that the Docker Compose script is run, the MySQL database needs a root password set. This parameter is used once and then ignored in the future.

After the first run, and you have created the “atlassian” and “healthcheck” users in MySQL, remove this parameter from the Docker Compose script and redeploy.

The Atlassian tools will then correctly use the Atlassian user to connect to the MySQL database and you can complete the installation.

- MYSQL_ROOT_PASSWORD={password}

Granting Permissions for the Atlassian User in MySQL

CREATE USER 'atlassian'@'localhost' IDENTIFIED BY '{password}';
GRANT USAGE ON *.* TO 'atlassian'@'localhost';
GRANT EXECUTE, SELECT, SHOW VIEW, ALTER, ALTER ROUTINE, CREATE, CREATE ROUTINE, CREATE TEMPORARY TABLES, CREATE VIEW, DELETE, DROP, EVENT, INDEX, INSERT, REFERENCES, TRIGGER, UPDATE, LOCK TABLES  ON `bitbucketdb`.* TO 'atlassian'@'localhost' WITH GRANT OPTION;
GRANT EXECUTE, SELECT, SHOW VIEW, ALTER, ALTER ROUTINE, CREATE, CREATE ROUTINE, CREATE TEMPORARY TABLES, CREATE VIEW, DELETE, DROP, EVENT, INDEX, INSERT, REFERENCES, TRIGGER, UPDATE, LOCK TABLES  ON `confluencedb`.* TO 'atlassian'@'localhost' WITH GRANT OPTION;
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, REFERENCES, INDEX, ALTER  ON `JIRADB`.* TO 'atlassian'@'localhost';
GRANT EXECUTE, SELECT, SHOW VIEW, ALTER, ALTER ROUTINE, CREATE, CREATE ROUTINE, CREATE TEMPORARY TABLES, CREATE VIEW, DELETE, DROP, EVENT, INDEX, INSERT, REFERENCES, TRIGGER, UPDATE, LOCK TABLES  ON `jiradb`.* TO 'atlassian'@'localhost' WITH GRANT OPTION;
FLUSH PRIVILEGES;

The Docker Compose Script in its Entirety

And there you have it: Three Atlassian tools deployed in Docker and using MySQL with one Compose file. With luck, all your Containers will be showing as “healthy” (as it does in my Portainer view below).

Portainer - Atlassian Healthcheck

The Final Docker Compose Script

version: '3'
services:
  atlassian-bitbucket:
    container_name: Bitbucket
    environment:
      - JDBC_PASSWORD={password}
      - JDBC_URL=jdbc:mysql://localhost:3306/bitbucketdb?useUnicode=true&characterEncoding=UTF8&sessionVariables=default_storage_engine=InnoDB&autoReconnect=true&useSSL=false
      - JDBC_USER=atlassian
      - SERVER_PROXY_NAME=bitbucket.yourdomain.com
      - SERVER_PROXY_PORT=443
      - SERVER_SCHEME=https
      - SERVER_SECURE=true
    depends_on:
      - mysql
    healthcheck:
      test: 'curl localhost:7990/status | grep -q RUNNING'
      interval: 1m
      start_period: 10m
      timeout: 10s
    image: 'atlassian/bitbucket:latest'
    network_mode: bridge
    ports:
      - '7990:7990'
      - '7999:7999'
    restart: unless-stopped
    volumes:
      - /docker/bitbucket:/var/atlassian/application-data/bitbucket:rw
      - /docker/configuration/mysql-connector-java-5.1.48-bin.jar:/var/atlassian/application-data/bitbucket/lib/mysql-connector-java-5.1.48-bin.jar:rw

  atlassian-confluence:
    container_name: Confluence
    environment:
      - ATL_DB_DRIVER=com.mysql.jdbc.Driver
      - ATL_DB_TYPE=mysql
      - ATL_JDBC_PASSWORD={password}
      - ATL_JDBC_URL=jdbc:mysql://localhost:3306/confluencedb?useUnicode=true&autoReconnect=true&useSSL=false
      - ATL_JDBC_USER=atlassian
      - ATL_PROXY_NAME=confluence.yourdomain.com
      - ATL_PROXY_PORT=443
      - ATL_TOMCAT_PORT=8090
      - ATL_TOMCAT_SCHEME=https
      - ATL_TOMCAT_SECURE=true
    depends_on:
      - mysql
    healthcheck:
      test: 'curl localhost:8090/status | grep -q RUNNING'
      interval: 1m
      start_period: 10m
      timeout: 10s
    image: 'atlassian/confluence:latest'
    network_mode: bridge
    ports:
      - '8090:8090'
      - '8091:8091'
    restart: unless-stopped
    volumes:
      - /docker/confluence:/var/atlassian/application-data/confluence:rw
      - /docker/configuration/mysql-connector-java-8.0.21.jar:/opt/atlassian/confluence/confluence/WEB-INF/lib/mysql-connector-java-8.0.21.jar:rw
      - /docker/confluence/logs:/opt/atlassian/confluence/logs:rw

  atlassian-jira:
    container_name: Jira
    environment:
      - ATL_DB_DRIVER=com.mysql.jdbc.Driver
      - ATL_DB_TYPE=mysql
      - ATL_JDBC_PASSWORD={password}
      - ATL_JDBC_URL=jdbc:mysql://localhost:3306/jiradb?useUnicode=true&autoReconnect=true&useSSL=false
      - ATL_JDBC_USER=atlassian
      - ATL_PROXY_NAME=jira.yourdomain.com
      - ATL_PROXY_PORT=443
      - ATL_TOMCAT_PORT=8080
      - ATL_TOMCAT_SCHEME=https
      - ATL_TOMCAT_SECURE=true
    depends_on:
      - mysql
    healthcheck:
      test: 'curl localhost:8080/status | grep -q RUNNING'
      interval: 1m
      start_period: 10m
      timeout: 10s
    image: 'atlassian/jira-software:latest'
    network_mode: bridge
    ports:
      - '8080:8080'
    restart: unless-stopped
    volumes:
      - /docker/jira:/var/atlassian/application-data/jira:rw
      - /docker/configuration/mysql-connector-java-8.0.21.jar:/opt/atlassian/jira/lib/mysql-connector-java-8.0.21.jar:rw
      - /docker/jira/log:/opt/atlassian/jira/logs:rw

  mysql:
    container_name: MySQL
    environment: 
      - MYSQL_ROOT_PASSWORD={password}
    healthcheck:
      test: '/usr/bin/mysql --user=healthcheck --password=healthcheck --execute "SHOW DATABASES;"'
      interval: 1m
      start_period: 1m
      timeout: 10s
    image: 'mysql:latest'
    network_mode: bridge
    ports:
      - '3306:3306'
    restart: unless-stopped
    volumes:
      - /docker/mysql:/var/lib/mysql:rw
      - /docker/configuration/conf.d/mysql.cnf:/etc/mysql/conf.d/my.cnf:rw

networks:
  default:
    external:
      name: bridge
Matthew Cunliffe

Matthew Cunliffe

Author

Matthew is an IT specialist with more than 24 years experience in software development and project management. He has a wide range of interests, including international political theory; playing guitar; music; hiking, kayaking, and bouldering; and data privacy and ethics in IT.

0 Comments

Submit a Comment

Your email address will not be published. Required fields are marked *

Share this post