Bitbucket Self-Hosted Runner Setup

๐Ÿ’ก Bitbucket Self-Hosted Runner Setup โ€” Executive Summary

Technical architectural analysis, practical configuration manifests, and deployment instructions for Bitbucket Self-Hosted Runner Setup.

On k8s

Sample
Exec
Create Bitbucket Runner
  1. Access https://bitbucket.org/\${organization}/\${repository}/admin/pipelines/runners

    1. Access the bitbucket Repository main page
    2. Access Repository settings at the bottom of the left sidebar
    3. Access Runners at the bottom of Pipelines
  2. Click Add runner button

  3. Select Linux Docker (x86_64) and specify a name and Next

  4. Duplicate the Docker execution statement.

bash
docker container run -it -v /tmp:/tmp -v /var/run/docker.sock:/var/run/docker.sock 
   -v /var/lib/docker/containers:/var/lib/docker/containers:ro 
   -e ACCOUNT_UUID=$(COPY)
   -e REPOSITORY_UUID=$(COPY)
   -e RUNNER_UUID=$(COPY)
   -e RUNTIME_PREREQUISITES_ENABLED=true 
   -e OAUTH_CLIENT_ID=$(COPY)
   -e OAUTH_CLIENT_SECRET=$(COPY)
   -e WORKING_DIRECTORY=/tmp 
   --name runner-2183e7de-fd6f-5468-b96c-3d75fc6c61b4 docker-public.packages.atlassian.com/sox/atlassian/bitbucket-pipelines-runner
  1. Label is an element managed by bitbucket, so if there is no separate policy, you can Finish
K8s runner creation and authentication link
    Complete the manifest by referring to
  1. .
  2. Copy the following from the previously cloned Docker executable statement.
    1. ACCOUNT_UUID
    2. REPOSITORY_UUID
    3. RUNNER_UUID
    4. OAUTH_CLIENT_ID
    5. OAUTH_CLIENT_SECRET
  3. Execute Job and Secret deployment
  4. k apply -f ${runner}.yaml
  5. Check normal operation of pod
  6. k get pods -n $ns
Confirm linking completion
  • Runner when first created

  • runner after pod running

Troubles
Container: docker-in-docker
  1. network is unreachable
yaml
Could not pull image: error pulling image configuration: Get https://docker-blobs.artifacts.atlassian.com/docker-public/docker/registry/v2/blobs/sha256/9e/9ea04d92255bdf0599c84facc7fd7be2e227bfbc07f511df9647a7ccfebdf59c/data?Expires=1745997666&Signature=4f55myfFz1Uzl5UPHc6kdhtTFQwBudvlxFnvi0Q0L-aFCoCOUVdGEulA6yC0aPHmLzPxvYhnqAY6ApBmXmdr6tvD0xXA2rGyxBfHMfxp2WYPHDEFcEa675ke5n3B-JBaGD~SQAHBsScuu~nh0ufPcO5rpJfK-4NHz0cLMUoQRnAb5i91NHeAvz5d23l6dPBvEXSLM5J~UAybGSIvO0YKNNnIOgpPpFlELiJL0xtn2QTlAydjzJncNu8CYEP0sDXJ8A1Ouw427lk~u3Y~SoFG35gbiubMIJxheFFbzVpEgyt9pAKAPIrvFNDFvjqNMb~KeN7qDXgwHKdVlujghJHO7w__&Key-Pair-Id=K3U3YXJT6R73J6: 
   dial tcp [2600:9000:2891:3400:15:4497:1cc0:93a1]:443: connect: 
   network is unreachable Refer to the Troubleshooting runners document for help resolving the issue.
  • Presumed to be a coredns issue
    2. failed to adjust OOM score for shim
yaml
time="2025-04-30T06:46:03.754488308Z" level=error msg="Handler for POST /containers/d7d238126da63910fcc0f34b273e2ab5ed66972011e13dee5517bd0d2d55f881/start returned error: io.containerd.runc.v2: failed to adjust OOM score for shim: set shim OOM score: write /proc/416/oom_score_adj: invalid argument\n: exit status 1: unknown"
  • Occurs when the runtime versions of CRI’s docker or containerd and DinD image are different.
    • CRI Runtime
yaml
docker version
       Client:
        Version:           26.1.3
        API version:       1.45
        Go version:        go1.22.2
        Git commit:        26.1.3-0ubuntu1~22.04.1
        Built:             Mon Oct 14 21:24:40 2024
        OS/Arch:           linux/amd64
        Context:           default

       Server:
        Engine:
         Version:          26.1.3
         API version:      1.45 (minimum version 1.24)
         Go version:       go1.22.2
         Git commit:       26.1.3-0ubuntu1~22.04.1
         Built:            Mon Oct 14 21:24:40 2024
         OS/Arch:          linux/amd64
         Experimental:     false
        containerd:
         Version:          1.7.24
         GitCommit:
        runc:
         Version:          1.1.12-0ubuntu2~22.04.1
         GitCommit:
        docker-init:
         Version:          0.19.0
         GitCommit:
 - Image runtime
yaml
- name: docker-in-docker
                     image: docker:26.1.3-dind
Container: runner
  1. failed to create task for container: 400
yaml
Status 400: {"message":"failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: 
   exec: \"/bin/sh\": stat /bin/sh: no such file or directory: unknown"} Refer to the Troubleshooting runners document for help resolving the issue.
  • This occurs when the container created by the runner java app is set to entrypoint=โ€™/bin/shโ€™, but the corresponding binary is not included in the creation request image (container).
Features
Mount docker daemon.json
yaml
#job/${JOB_NAME}-runner
#.spec.template.spec.containers[name=docker-in-docker].volumeMounts
                - name: docker-daemon-cm
                  mountPath: /etc/docker/daemon.json
                  subPath: daemon.json
#.spec.template.spec.volumes
            - name: docker-daemon-cm
              configMap:
                name: docker-daemon
Added option to enable exposure of Pipeline Log to Container Log
yaml
#job/${JOB_NAME}-runner
#.spec.template.spec.containers[name=runner].env
                - name: BITBUCKET_PIPELINES_RUNNER_BUILD_LOG_ENABLED
                  value: 'TRUE'
Use Custom Logback configuration
  • logback samples
default
xml
<configuration>
      <variable name="BASE_LOG_PATH" value="/tmp" />
      <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
          <filter class="ch.qos.logback.core.filter.EvaluatorFilter">
              <evaluator>
                  <expression>com.atlassian.pipelines.stargate.client.core.exceptions.StargateForbiddenException.class.isInstance(throwable)</expression>
              </evaluator>
              <onMatch>DENY</onMatch>
          </filter>
          <encoder>
              <!-- print [buildLogName] from SystemLogAppenderFilter if exists, otherwise do not print empty [] -->
              <pattern>[%date{ISO8601}]%replace([%X{buildLogName}]){'\[\]',''} %message%n%rootException</pattern>
          </encoder>
      </appender>
      <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
          <file>${BASE_LOG_PATH}/runner.log</file>
          <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
              <fileNamePattern>${BASE_LOG_PATH}/runner.%d.%i.log</fileNamePattern>
              <maxHistory>7</maxHistory>
              <maxFileSize>100MB</maxFileSize>
              <totalSizeCap>1GB</totalSizeCap>
          </rollingPolicy>
          <encoder>
              <!-- print [buildLogName] from SystemLogAppenderFilter if exists, otherwise do not print empty [] -->
              <pattern>[%date{ISO8601}]%replace([%X{buildLogName}]){'\[\]',''} %message%n%rootException</pattern>
          </encoder>
      </appender>
      <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
          <resetJUL>true</resetJUL>
      </contextListener>
      <root level="INFO">
          <appender-ref ref="STDOUT" />
          <appender-ref ref="FILE" />
      </root>
      <logger name="com.atlassian.pipelines.runner" level="${BITBUCKET_PIPELINES_RUNNER_LOG_LEVEL:-INFO}" />
      <logger name="build" level="INFO" />
      <logger name="build.service.auth-proxy" level="OFF" />
      <logger name="com.netflix" level="OFF" />
      <logger name="com.atlassian.pipelines.stargate" level="OFF" />
      <logger name="com.github.dockerjava" level="OFF" />
  </configuration>
Json style
xml
<configuration>
    <variable name="BASE_LOG_PATH" value="/tmp" />
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
      <filter class="ch.qos.logback.core.filter.EvaluatorFilter">
        <evaluator>
          <expression>com.atlassian.pipelines.stargate.client.core.exceptions.StargateForbiddenException.class.isInstance(throwable)</expression>
        </evaluator>
        <onMatch>DENY</onMatch>
      </filter>
      <encoder class="net.logstash.logback.encoder.LogstashEncoder" />
    </appender>
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
      <file>${BASE_LOG_PATH}/runner.log</file>
      <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
        <fileNamePattern>${BASE_LOG_PATH}/runner.%d.%i.log</fileNamePattern>
        <maxHistory>7</maxHistory>
        <maxFileSize>100MB</maxFileSize>
        <totalSizeCap>1GB</totalSizeCap>
      </rollingPolicy>
      <encoder>
        <!-- print [buildLogName] from SystemLogAppenderFilter if exists, otherwise do not print empty [] -->
        <pattern>[%date{ISO8601}]%replace([%X{buildLogName}]){'\[\]',''} %message%n%rootException</pattern>
      </encoder>
    </appender>
    <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
      <resetJUL>true</resetJUL>
    </contextListener>
    <root level="INFO">
      <appender-ref ref="STDOUT" />
      <appender-ref ref="FILE" />
    </root>
    <logger name="com.atlassian.pipelines.runner" level="${BITBUCKET_PIPELINES_RUNNER_LOG_LEVEL:-INFO}" />
    <logger name="build" level="INFO" />
    <logger name="build.service.auth-proxy" level="OFF" />
    <logger name="com.netflix" level="OFF" />
    <logger name="com.atlassian.pipelines.stargate" level="OFF" />
    <logger name="com.github.dockerjava" level="OFF" />
  </configuration>
  • Create logback configmap
bash
kubectl create configmap -n bitbucket-runner logback-config --from-file=custom-logback.xml
  • Add environment variable and volume mount
yaml
#job/${JOB_NAME}-runner
  #.spec.template.spec.containers[name=runner].env
                  - name: LOGBACK_CONFIGURATION_FILE
                    value: /tmp/custom-logback.xml
  #.spec.template.spec.containers[name=runner].volumeMounts
                  - name: logback-config
                    mountPath: /tmp/custom-logback.xml
                    subPath: custom-logback.xml
                    readOnly: true
  #.spec.template.spec.volumes
              - name: logback-config
                configMap:
                  name: logback-config
Use Insecure Docker Registry
References and Guidelines (NOTE)

For security reasons, the Docker daemon cannot be configured in Pipelines Cloud Runners. However, Linux self-hosted runners allow custom configuration for the Docker daemon, which can lead to the use of insecure registries.

  • Dockerfile
References and Guidelines (NOTE)

The service does not support entrypoint overwrite, so building a custom image is required.

docker
FROM docker:26.1.3-dind
  ENTRYPOINT [ "sh", "-c", "dockerd-entrypoint.sh $DOCKER_OPTS" ]
  • pipelines.yml

  • self-hosted pipeline using custom defined service

yaml
pipelines:
      branches:
        "feature/pipeline-test":
          - step:
              name: "on-premise-agent :: Images push & Kubernetes Deploy"
              runs-on:
                - self.hosted
              services:
                - custom-dind
              image: { client input }
              script:
                - docker info
  • Definitions for using a Docker-in-Docker (DIND) image from a private registry.
yaml
# private registry
    definitions:
      services:
        custom-dind:
          image:
            name: { insecure-dind input }
            username: "$private_docker_hub"
            password: "$private_docker_hub_pw"
          type: docker
          variables:
            DOCKER_OPTS: "--insecure-registry={ registry input }"
  • Definitions for using a Docker-in-Docker (DIND) image from a public registry.
yaml
# public registry
    definitions:
      services:
        custom-dind:
          image: { insecure-dind input }
          type: docker
          variables:
            DOCKER_OPTS: "--insecure-registry={ registry input }"

Comments

Leave a Reply

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