Bitbucket Server Installation

The installation for Bitbucket itself brings another major challenge, specifically around how configuration files are handled. The good news though, the dockerfiles are relatively unchanged from their source and available at https://hub.docker.com/r/atlassian/bitbucket-server/dockerfile and the available instructions at https://confluence.atlassian.com/bitbucketserver/automated-setup-for-bitbucket-server-776640098.html.

labimagesbitbucket
setup.displayName=Bitbucket
setup.baseUrl=https://bitbucket.acme.com

setup.license=BITBUCKET_LICENSE

setup.sysadmin.username=admin
setup.sysadmin.password=admin123
setup.sysadmin.displayName=Admin User
setup.sysadmin.emailAddress=admin@acme.com

#jdbc.driver=org.postgresql.Driver
#jdbc.url=jdbc:postgresql://d1i-doc-dbpg01:5432/bitbucket
jdbc.driver=com.microsoft.sqlserver.jdbc.SQLServerDriver
jdbc.url=jdbc:sqlserver://d1i-doc-dbss01:1433;databaseName=bitbucket;
jdbc.user=bitbucketuser
jdbc.password=ZeCodeIzGood

# run behind nginx ssl
#server.port=7990
#server.secure=true
#server.scheme=https
#server.proxy-port=443
#server.proxy-name=bitbucket.acme.com
version: '3.3'

services:
  bitbucket:
    image: infra/atlassian/bitbucket:1.0.0
    build:
      context: .
      network: host
      args:
        ARG_ART_URL: http://d1i-doc-ngbuild:3001
      extra_hosts:
        - "d1i-doc-ngbuild:172.22.90.2"
FROM infra/java/openjdk-8:1.0.0

# https://bitbucket.org/atlassian-docker/docker-atlassian-bitbucket-server/src/master/

ENV RUN_USER            					daemon
ENV RUN_GROUP           					daemon

# https://confluence.atlassian.com/display/BitbucketServer/Bitbucket+Server+home+directory
ENV BITBUCKET_HOME          				/var/atlassian/application-data/bitbucket
ENV BITBUCKET_INSTALL_DIR   				/opt/atlassian/bitbucket

WORKDIR $BITBUCKET_HOME

CMD ["/entrypoint.sh", "-fg"]
ENTRYPOINT ["/tini", "--"]

ARG ARG_ART_URL

RUN sed -e "s|APT_URL|${ARG_ART_URL}|" /etc/apt/sources.list.base > /etc/apt/sources.list \
    && apt-get update \
    && apt-get install -y --no-install-recommends fontconfig git perl dos2unix \
    && apt-get clean \
    && rm /etc/apt/sources.list

ARG ARG_ART_URL

RUN curl $ARG_ART_URL/repository/dml/docker/tools/tini -o /tini \
    && chmod +x /tini

COPY entrypoint.sh /entrypoint.sh
RUN dos2unix /entrypoint.sh \
    && chmod +x /entrypoint.sh

ARG DOWNLOAD_URL=$ARG_ART_URL/repository/dml/docker/bitbucket/atlassian-bitbucket-7.21.3.tar.gz

COPY bitbucket.properties $BITBUCKET_HOME/shared/bitbucket.properties

RUN mkdir -p                             	${BITBUCKET_INSTALL_DIR} \
    && curl -L                              ${DOWNLOAD_URL} | tar -xz --strip-components=1 -C "${BITBUCKET_INSTALL_DIR}" \
    && chown -R ${RUN_USER}:${RUN_GROUP} 	${BITBUCKET_INSTALL_DIR}/ \
    && sed -i -e 's/^# umask/umask/' 		${BITBUCKET_INSTALL_DIR}/bin/_start-webapp.sh

#!/bin/bash
set -euo pipefail

# Set recommended umask of "u=,g=w,o=rwx" (0027)
umask 0027

# Setup Catalina Opts
: ${CATALINA_CONNECTOR_PROXYNAME:=}
: ${CATALINA_CONNECTOR_PROXYPORT:=}
: ${CATALINA_CONNECTOR_SCHEME:=http}
: ${CATALINA_CONNECTOR_SECURE:=false}

: ${CATALINA_OPTS:=}

: ${JAVA_OPTS:=}

: ${ELASTICSEARCH_ENABLED:=true}
: ${APPLICATION_MODE:=}

CATALINA_OPTS="${CATALINA_OPTS} -DcatalinaConnectorProxyName=${CATALINA_CONNECTOR_PROXYNAME}"
CATALINA_OPTS="${CATALINA_OPTS} -DcatalinaConnectorProxyPort=${CATALINA_CONNECTOR_PROXYPORT}"
CATALINA_OPTS="${CATALINA_OPTS} -DcatalinaConnectorScheme=${CATALINA_CONNECTOR_SCHEME}"
CATALINA_OPTS="${CATALINA_OPTS} -DcatalinaConnectorSecure=${CATALINA_CONNECTOR_SECURE}"

JAVA_OPTS="${JAVA_OPTS} ${CATALINA_OPTS}"

ARGS="$@"

# Start Bitbucket without Elasticsearch
if [ "${ELASTICSEARCH_ENABLED}" == "false" ] || [ "${APPLICATION_MODE}" == "mirror" ]; then
    ARGS="--no-search ${ARGS}"
fi

# Start Bitbucket as the correct user.
if [ "${UID}" -eq 0 ]; then
    echo "User is currently root. Will change directory ownership to ${RUN_USER}:${RUN_GROUP}, then downgrade permission to ${RUN_USER}"
    PERMISSIONS_SIGNATURE=$(stat -c "%u:%U:%a" "${BITBUCKET_HOME}")
    EXPECTED_PERMISSIONS=$(id -u ${RUN_USER}):${RUN_USER}:700
    if [ "${PERMISSIONS_SIGNATURE}" != "${EXPECTED_PERMISSIONS}" ]; then
        echo "Updating permissions for BITBUCKET_HOME"
        mkdir -p "${BITBUCKET_HOME}/lib" &&
            chmod -R 700 "${BITBUCKET_HOME}" &&
            chown -R "${RUN_USER}:${RUN_GROUP}" "${BITBUCKET_HOME}"
    fi
    # Now drop privileges
    exec su -s /bin/bash "${RUN_USER}" -c "${BITBUCKET_INSTALL_DIR}/bin/start-bitbucket.sh ${ARGS}"
else
    exec "${BITBUCKET_INSTALL_DIR}/bin/start-bitbucket.sh" ${ARGS}
fi

The key to this image is the bitbucket.properties file where all the automation magic occurs. Under normal circumstances, configuration files that contain information about your organization should not be baked into the image and rather be injected. However, Bitbucket Server will use this properties file during its initial startup AND modify the file during installation (thus needing write access and permissions). Modifying a properties file here is the major challenge here when looking at docker container architecture. Since we need to make changes to the same properties file after the initial installation to run it behind a proxy, we will have to follow additional steps:

  1. Startup a fresh Bitbucket with new data volume and connected to a pre-setup empty Database
  2. Wait for Bitbucket initialization which will populate database and modify the properties file on the bitbucket volume
  3. Stop Bitbucket and start another sidecar to modify the properties file (inside the bitbucket volume)
  4. Restart bitbucket

Building and Running

Before the build, add your license key to the properties file to be included in the Docker build.

sed -e "s|BITBUCKET_LICENSE|YOUR-LICENSE-HERE|" images/bitbucket/bitbucket.properties.base > images/bitbucket/bitbucket.properties

Build it with your properties file.

docker-compose -f images/bitbucket/docker-compose.yml build

#docker login docker-private.acme.com
images/docker-push.sh infra/atlassian/bitbucket:1.0.0 docker-private.acme.com

As we have already completed our database volume, we simply start Bitbucket via the below compose for the initial install to prep our bitbucket docker volume.

labcomposed
version: '3.3'

services:
  bitbucket:
    image: infra/atlassian/bitbucket:1.0.0
    volumes:
      - bitbucket-data:/var/atlassian/application-data/bitbucket
    ports:
      - 7990:7990
      - 7999:7999
    networks:
      - ops-network
networks:
  ops-network:
    name: ops-network
volumes:
  bitbucket-data:
    name: bitbucket-data

Run the initialization.

docker-compose -f composed/docker-compose-bitbucket-init.yml up

The install will take aprox 2 minutes total and once completed will show you the login page at http://localhost:7990 with the ability to login as admin:admin123. Once completed stop the container.

Running it behind NGINX

As stated during our Nexus3 Infrastructure Install, we want to re-use our NGINX that currently proxies Nexus to now also include bitbucket so we can access it via https://bitbucket.acme.com.

This base NGINX will create a wildcard self signed SSL under *.acme.com allowing us to run many of our DevOps apps behind the same nginx as we continue.

While it may appear adding a new nginx config is enough, we have to go back to the current Bitbucket installation and make modifications to /var/atlassian/application-data/bitbucket/shared/bitbucket.properties which was modified during our initial install. We had prepared the original properties file already to make it easier to change after install by simply uncommenting those settings.

# run behind nginx ssl
#server.port=7990
#server.secure=true
#server.scheme=https
#server.proxy-port=443
#server.proxy-name=bitbucket.acme.com

As this is contained within the docker volume, we need yet again another process.

labimagesbitbucket-proxy-init
version: '3.3'

services:
  bitbucket-proxy-init:
    image: infra/atlassian/bitbucket-proxy-init:1.0.0
    build:
      context: .
      network: host
      args:
        ARG_ART_URL: http://d1i-doc-ngbuild:3001
      extra_hosts:
        - "d1i-doc-ngbuild:172.22.90.2"
FROM infra/ubuntu/focal:1.0.0

ARG ARG_ART_URL

RUN sed -e "s|APT_URL|${ARG_ART_URL}|" /etc/apt/sources.list.base > /etc/apt/sources.list \
    && apt-get update \
    && apt-get install -y dos2unix \
    && apt-get clean \
    && rm /etc/apt/sources.list

COPY init.sh /init/init.sh
RUN chmod +x /init/init.sh \
    && dos2unix /init/init.sh

ENTRYPOINT ["/init/init.sh"]
#!/bin/bash

# fail if anything errors
set -e

# move it to backup
mv /var/atlassian/application-data/bitbucket/shared/bitbucket.properties /var/atlassian/application-data/bitbucket/shared/bitbucket.properties.bak2

# uncomment
sed -e "s|#server|server|" /var/atlassian/application-data/bitbucket/shared/bitbucket.properties.bak2 > /var/atlassian/application-data/bitbucket/shared/bitbucket.properties

# output it again
echo "Proxy changes completed"

Running the update is done via the following compose file that connects the appropriate docker volume.

labcomposed
version: '3.3'

services:
  bitbucket:
    image: infra/atlassian/bitbucket-proxy-init:1.0.0
    volumes:
      - bitbucket-data:/var/atlassian/application-data/bitbucket
    networks:
      - ops-network
networks:
  ops-network:
    name: ops-network
volumes:
  bitbucket-data:
    name: bitbucket-data

Build and run.

docker-compose -f images/bitbucket-proxy-init/docker-compose.yml build
docker-compose -f composed/docker-compose-bitbucket-proxy-init.yml up

Adding Bitbucket to Our CI/CD Apps

At this point we are already running several containers. Those are nginx-build, nginx for ssl, nexus, database. We will be adding bitbucket that is now fully configured via the following compose.

labcomposed
version: '3.3'

services:
  bitbucket:
    image: infra/atlassian/bitbucket:1.0.0
    volumes:
      - bitbucket-data:/var/atlassian/application-data/bitbucket
    ports:
      - 7990:7990
      - 7999:7999
    hostname: d1i-doc-bitbucket01
    networks:
      - ops-network
networks:
  ops-network:
    name: ops-network
volumes:
  bitbucket-data:
    name: bitbucket-data

We will be reusing the nginx we are running for nexus and using a new compose that combines both nexus and bitbucket.

labcomposed
server {
    listen       *:443 ssl;
    server_name  "nexus.acme.com";

    ssl_certificate           /etc/nginx/external/cert.pem;
    ssl_certificate_key       /etc/nginx/external/key.pem;

    client_max_body_size 1G;

    location / {
        proxy_pass         http://d1i-doc-nexus01:8081;

        proxy_set_header   Host             $host;
        proxy_set_header   X-Real-IP        $remote_addr;
        proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
	    proxy_set_header   X-Forwarded-Proto https;
    }
}

server {
    listen       *:443 ssl;
    server_name  "docker.acme.com";

    ssl_certificate           /etc/nginx/external/cert.pem;
    ssl_certificate_key       /etc/nginx/external/key.pem;

    client_max_body_size 1G;

    location / {
        proxy_pass         http://d1i-doc-nexus01:8082;

        proxy_set_header   Host             $host;
        proxy_set_header   X-Real-IP        $remote_addr;
        proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
	    proxy_set_header   X-Forwarded-Proto https;
    }
}



server {
    listen       *:443 ssl;
    server_name  "docker-private.acme.com";

    ssl_certificate           /etc/nginx/external/cert.pem;
    ssl_certificate_key       /etc/nginx/external/key.pem;

    client_max_body_size 1G;

    location / {
        proxy_pass         http://d1i-doc-nexus01:8083;

        proxy_set_header   Host             $host;
        proxy_set_header   X-Real-IP        $remote_addr;
        proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
	    proxy_set_header   X-Forwarded-Proto https;
    }
}
server {
    listen       *:443 ssl;
    server_name  "bitbucket.acme.com";

    ssl_certificate           /etc/nginx/external/cert.pem;
    ssl_certificate_key       /etc/nginx/external/key.pem;

    client_max_body_size 1G;

    location / {
        proxy_pass         http://d1i-doc-bitbucket01:7990;

        proxy_set_header   Host             $host;
        proxy_set_header   X-Real-IP        $remote_addr;
        proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto https;
    }
}
version: '3.3'

services:
  nginx:
    image: infra/nginx/base:1.0.0
    volumes:
      - ./ssl/cert.pem:/etc/nginx/external/cert.pem
      - ./ssl/key.pem:/etc/nginx/external/key.pem
      - ./nginx/nexus.conf:/etc/nginx/conf.d/nexus.conf
      - ./nginx/bitbucket.conf:/etc/nginx/conf.d/bitbucket.conf
    ports:
      - 443:443
    hostname: d1i-doc-ng01
    networks:
      ops-network:
        ipv4_address: 172.22.90.1
networks:
  ops-network:
    name: ops-network
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 172.22.90.0/16

Add our bitbucket container to the running containers and also add a new nginx config to run behind the same nginx instance.

# now start it up so we can put it behind nginx
docker-compose -f composed/docker-compose-bitbucket.yml up --no-start
docker-compose -f composed/docker-compose-bitbucket.yml start

# stop the nexusnginx and start the combined one
docker-compose -f composed/docker-compose-nginx-nexus.yml stop
docker-compose -f composed/docker-compose-nginx-nexus-bitbucket.yml up --no-start
docker-compose -f composed/docker-compose-nginx-nexus-bitbucket.yml start

We now have both Nexus and Bitbucket running via SSL behind our nginx.