Skip to main content

Build and sign application containers

At the end of this tutorial you will learn how to sign a container image without using a cosign.


Before you get started with the tutorial make sure you have completed the Build Go Application Containers CI Pipeline.

Ensure the following tools are installed locally:


As part of the Build Go Application Containers CI Pipeline tutorial we saw how to build a Go application container image using a CI pipeline. In this tutorial we improve the pipeline to sign the container image and ensure its authenticity. Please read History and Research to learn why we need to secure the software supply chain.

Generate signing keys

  1. Assuming you have the fork of, navigate to the folder where it was cloned. Refer to this folder as $TUTORIAL_HOME.
  1. Generate a random password and store it in a variable called $COSIGN_PASSWORD,
export COSIGN_PASSWORD=$(openssl rand -hex 16)

Make sure to note down the $COSIGN_PASSWORD as it might be needed later.

  1. Once you have generated the password, generate the key pair that we will use to sign.
cosign generate-key-pair

The command generates the cosign.key and under $TUTORIAL_HOME.


Make sure the cosign.key, are excluded from your SCM. In other words, add them to .gitignore

Create signing secrets

  1. Navigate to Project Setup, and then select Secrets.

Project Secrets

  1. Select + New Secret , and then select Text.

New Text Secret

  1. Set the Secret Name to be cosign_password, and then fill your cosign private key password $COSIGN_PASSSWORD as Secret Value.

Cosign Password

  1. Select + New Secret, and then select File.

New File Secret

  1. Fill the details for the secret cosign_private_key as shown.

Cosign Private Key Secret

  1. For the Select File, browse and pick the cosign.key from the $TUTORIAL_HOME.

  2. Repeat the same process of creating another __File__ secret to store the cosign public key, this time using the file from the $TUTORIAL_HOME.

  3. Fill the details for the secret cosign_public_key as shown.

Cosign Public Key Secret

With this we now have four secrets in our project:

Project Secrets

Update the pipeline to sign the container Image

We now have all of the required resources: cosign_password, cosign_private_key and cosign_public_key to sign the container image using cosign.

Now, update the existing build and push step of our Build Go pipeline to sign the fruits-api container image.

  1. Navigate to Projects, and then Pipelines.

Pipelines List

  1. Select Build Go pipeline to open the pipeline editor.

Build Go Pipeline

  1. Select the step build and push and update the Command to be as shown:


echo -n "$DOCKER_HUB_PASSWORD" | ko auth login -u "$DOCKER_HUB_USERNAME" --password-stdin
IMAGE_REF=$(ko build --bare --platform linux/amd64 --platform linux/arm64 .)
cosign sign --key env://COSIGN_PRIVATE_KEY $IMAGE_REF

Build and Push Step

We also need to configure few environment variables that are required by ko and cosign to build and push the signed image to fruits-api container repository.

  1. Update the Environment Variables section with following values:
DOCKER_HUB_PASSWORD: <+secrets.getValue("docker_hub_password")>
COSIGN_PRIVATE_KEY: <+secrets.getValue("cosign_private_key")>
COSIGN_PASSWORD: <+secrets.getValue("cosign_password")>

Build and Push Env

The step also exposes an output variable called IMAGE_REF which could be used by other steps, for example to verify the signature by using pipeline expression <+steps.build_and_push.output.outputVariables.IMAGE_REF>.

  • As marked, ensure the DOCKER_HUB_PASSWORD,COSIGN_PRIVATE_KEY and COSIGN_PASSWORD are of the Expression type.
  • secrets.getValue is an expression that allows you to get the value from the secret docker_hub_password that was created earlier in the tutorial. Check the docs for more information.
  • All $DOCKER_HUB_USERNAME references should your Docker Hub Username.
  1. Select Apply Changes to save the step, and then select Save to save the pipeline.

Final Pipeline

With those changes saved, you are ready to lint, test, build and push your go application to container registry(DockerHub).

Run the CI pipeline

  1. As you did earlier, select Run from the pipeline editor window.

Run Pipeline

  1. Leaving all the default entries in place, namely Git Branch and Branch Name to be main, select Run Pipeline to start the pipeline run.

  2. Now you are ready to execute. Select Run Pipeline.

  3. After the pipeline runs successfully, head back to Docker Hub to check the image references.


As you see the logs the container was built and the image reference is signed using our $COSIGN_PRIVATE_KEY.

Sign logs

Verify the signed image

To verify the image signature, use the public key $COSIGN_PUBLIC_KEY against the image sha256 digest.

To get the digest of an image you can use crane.

export IMAGE_REF=$(crane digest$DOCKER_USERNAME/fruits-api:latest)

Run the following command to verify the signature using our cosign public key,

cosign verify --key "$TUTORIAL_HOME/" \
"$DOCKER_USERNAME/[email protected]$IMAGE_REF"

The command should show an output as shown if the signature is valid, with $DOCKER_USERNAME and $IMAGE_REF replacing your Docker Hub username and image digest from the previous step.

Verification for$DOCKER_USERNAME/[email protected]$IMAGE_REF --
The following checks were performed on each of these signatures:
- The cosign claims were validated
- The signatures were verified against the specified public key

[{"critical":{"identity":{"docker-reference":"$DOCKER_USERNAME/fruits-api"},"image":{"docker-manifest-digest":"$IMAGE_REF"},"type":"cosign container image signature"},"optional":null}]

In an ideal scenario the verification is done by governance tools like OPA or kyverno. The integration with those tools can be done using Harness Platform with Harness Continuous Delivery.


Can you update the pipeline to a sign verification step?

Clue: Use $IMAGE_REF from build and push step output variables.

Continuing on your continuous integration journey

You can now execute your builds whenever you want in a consistent fashion. You can modify the trigger to watch for SCM events so upon commit, for example, the pipeline is kicked off automatically. All of the objects you create are available for you to reuse. Lastly, you can even save your backing work / have it as part of your source code. Everything that you do in Harness is represented by YAML; feel free to store it as part of your project.

After you have built your artifact, the next step is to deploy your artifact. This is where Continuous Delivery steps in. Make sure to check out some other CD Tutorials.