Promote Artifacts with Skopeo Copy

Feature Overview

Artifact promotion moves already-built artifacts from one trusted location to another, such as from a development image repository to a staging or production repository. For container images, use the skopeo-copy Task to copy images without rebuilding them.

When the promotion requires human review, place a manual approval step before skopeo-copy. The approval step pauses the PipelineRun until the required approvers approve the promotion, and the copy step starts only after approval succeeds.

Use Cases

  • Promote images from development, test, or staging repositories to production.
  • Promote several related images in one auditable promotion run.
  • Require a release manager or release group to approve production promotion.
  • Keep build pipelines and promotion pipelines separate, so production artifacts are copied from immutable build outputs instead of rebuilt.

Prerequisites

  • Tekton Pipelines is installed.
  • The skopeo-copy Task is available from the configured Hub catalog.
  • Manual Approval Gate is deployed for the approval workflow shown in this guide. For approval setup and operation details, see Manual Approval Gate.
  • Source and destination registry credentials are prepared as Kubernetes Secret objects in the namespace where the PipelineRun runs.
  • The pipeline author can create or update Pipeline resources, and the run user can create PipelineRun resources in the target namespace.
  • You understand basic Pipeline, PipelineRun, Task, and Workspace concepts. If needed, review Pipeline, PipelineRun, Task, and Workspace.

Promotion Flow

A typical image promotion pipeline contains two stages:

  1. wait-for-approval: requires designated approvers to approve the promotion.
  2. promote-images: copies one or more images with skopeo-copy.

Keep the approval policy in the Pipeline definition. Do not expose approval policy parameters such as approvers, numberOfApprovalsRequired, or approval rules as PipelineRun parameters. If those values are exposed at run time, a pipeline executor could override the default approval rule and bypass the intended release control.

The PipelineRun should only provide values that legitimately change per run, such as the source and destination image mappings.

Steps

1. Prepare Registry Credentials

Create one secret for the source registry and another for the destination registry. The following examples use kubernetes.io/dockerconfigjson secrets:

kubectl create secret docker-registry src-registry-config \
  -n <namespace> \
  --docker-server=dev-registry.example.com \
  --docker-username=<source-username> \
  --docker-password=<source-password>

kubectl create secret docker-registry dst-registry-config \
  -n <namespace> \
  --docker-server=prod-registry.example.com \
  --docker-username=<destination-username> \
  --docker-password=<destination-password>

If the same credential can access both registries, you can bind the same secret to both workspaces.

2. Create the Promotion Pipeline

The following pipeline fixes the approval policy in the Pipeline and exposes only imageMappings, srcTLSVerify, and dstTLSVerify as run-time inputs. Replace release-manager and group:release-approvers with the canonical user and group identifiers from your identity provider. For identifier details, see User and group identifiers.

apiVersion: tekton.dev/v1
kind: Pipeline
metadata:
  name: promote-artifacts-with-approval
spec:
  params:
    - name: imageMappings
      type: array
      description: Source-to-destination image mappings for this promotion run.
    - name: srcTLSVerify
      type: string
      default: "true"
      description: Enable TLS verification for the source registry.
    - name: dstTLSVerify
      type: string
      default: "true"
      description: Enable TLS verification for the destination registry.
  workspaces:
    - name: src-registry-config
    - name: dst-registry-config
  tasks:
    - name: wait-for-approval
      taskRef:
        apiVersion: openshift-pipelines.org/v1alpha1
        kind: ApprovalTask
      timeout: "24h"
      params:
        - name: approvers
          value:
            - release-manager
            - group:release-approvers
        - name: numberOfApprovalsRequired
          value: "1"
        - name: description
          value: "Approve artifact promotion to the production registry."

    - name: promote-images
      runAfter:
        - wait-for-approval
      taskRef:
        resolver: hub
        params:
          - name: catalog
            value: catalog
          - name: kind
            value: task
          - name: name
            value: skopeo-copy
          - name: version
            value: "0.1"
      params:
        - name: imageMappings
          value:
            - $(params.imageMappings[*])
        - name: srcTLSVerify
          value: $(params.srcTLSVerify)
        - name: dstTLSVerify
          value: $(params.dstTLSVerify)
      workspaces:
        - name: src-registry-config
          workspace: src-registry-config
        - name: dst-registry-config
          workspace: dst-registry-config

The imageMappings parameter accepts one SRC DST mapping per array item. Each image reference must include the skopeo transport prefix, such as docker://. When imageMappings is not empty, skopeo-copy uses it for batch promotion and ignores srcImage and dstImages.

3. Start a Promotion Run

Create a PipelineRun and provide the images that need to be promoted. For multiple images, put every source-to-destination pair in imageMappings:

apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
  name: promote-artifacts-prod-run
spec:
  pipelineRef:
    name: promote-artifacts-with-approval
  timeouts:
    pipeline: 72h
    tasks: 72h
  params:
    - name: imageMappings
      value:
        - docker://dev-registry.example.com/team/app-api:1.2.3 docker://prod-registry.example.com/team/app-api:1.2.3
        - docker://dev-registry.example.com/team/app-worker:1.2.3 docker://prod-registry.example.com/team/app-worker:1.2.3
    - name: srcTLSVerify
      value: "true"
    - name: dstTLSVerify
      value: "true"
  workspaces:
    - name: src-registry-config
      secret:
        secretName: src-registry-config
    - name: dst-registry-config
      secret:
        secretName: dst-registry-config

Set timeouts.pipeline and timeouts.tasks long enough for the expected approval window. The approval task timeout should be shorter than the PipelineRun timeout. In this example, the approval task can wait up to 24h, while the whole PipelineRun can run for up to 72h.

Alternative: Promote One Image to Multiple Destinations

If you are promoting one source image to one or more target tags, you can also use srcImage and dstImages instead of imageMappings:

tasks:
  - name: promote-image
    taskRef:
      resolver: hub
      params:
        - name: catalog
          value: catalog
        - name: kind
          value: task
        - name: name
          value: skopeo-copy
        - name: version
          value: "0.1"
    params:
      - name: srcTransport
        value: registry
      - name: srcImage
        value: dev-registry.example.com/team/app-api:1.2.3
      - name: dstTransport
        value: registry
      - name: dstImages
        value:
          - prod-registry.example.com/team/app-api:1.2.3
          - prod-registry.example.com/team/app-api:stable

Use this form only when one source image needs one or more destination tags. Use imageMappings when a promotion run contains multiple source images.

Operation Results

  • The PipelineRun stops at wait-for-approval until the required approver or group approves it.
  • If the approval is rejected, the PipelineRun fails and the promotion copy does not start.
  • If the approval succeeds, skopeo-copy copies each mapping to the destination registry.
  • skopeo-copy emits digest-oriented results for successful registry destinations, including IMAGES, dst-IMAGE_URL, dst-IMAGE_DIGEST, dst-image-urls, and dst-image-tag.

Use the following commands to inspect the run:

kubectl get pipelinerun promote-artifacts-prod-run -n <namespace>

kubectl get approvaltask promote-artifacts-prod-run-wait-for-approval \
  -n <namespace> \
  -o jsonpath='{.status.state}{"\n"}'

PROMOTE_TASKRUN="$(kubectl get taskrun \
  -n <namespace> \
  -l tekton.dev/pipelineRun=promote-artifacts-prod-run,tekton.dev/pipelineTask=promote-images \
  -o jsonpath='{.items[0].metadata.name}')"

kubectl get taskrun "${PROMOTE_TASKRUN}" \
  -n <namespace> \
  -o jsonpath='{range .status.results[*]}{.name}: {.value}{"\n"}{end}'

PROMOTE_POD="$(kubectl get taskrun "${PROMOTE_TASKRUN}" \
  -n <namespace> \
  -o jsonpath='{.status.podName}')"

kubectl logs "${PROMOTE_POD}" -n <namespace> -c step-copy-images

For approval audit details, inspect status.approversResponse on the generated ApprovalTask.

Troubleshooting

  • Promotion starts without the expected gate: Confirm the skopeo-copy task has runAfter: [wait-for-approval] or otherwise depends on the approval task.
  • A run user can change approvers: Remove approval policy values from PipelineRun parameters and keep them in a protected Pipeline definition.
  • Approval task never appears: Confirm Manual Approval Gate is installed and ready, then inspect the CustomRun and ApprovalTask resources.
  • Registry authentication fails: Verify that the source and destination secrets contain credentials for the registries used in imageMappings.
  • TLS verification fails for an internal registry: Prefer mounting the correct CA bundle. Set srcTLSVerify or dstTLSVerify to "false" only for a trusted internal registry that intentionally uses insecure access or self-signed certificates.
  • Only some mappings were copied: Inspect the promote-images TaskRun logs. Each imageMappings item must contain exactly two references: SRC DST.

Learn More