diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 86e893d2..9716b97d 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -96,7 +96,7 @@ unit-tests:
   stage: package-build
   timeout: 2h 30m
   script:
-    - ./scripts/release.sh ${DIST}-${ARCH}
+    - ./scripts/build-packages.sh ${DIST}-${ARCH}
 
   artifacts:
     name: ${ARTIFACTS_NAME}
diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md
index 33d48894..3d9419d1 100644
--- a/DEVELOPMENT.md
+++ b/DEVELOPMENT.md
@@ -13,7 +13,7 @@ The `nvidia-container-toolkit` resides in this repo directly.
 
 In oder to build the packages, the following command is executed
 ```sh
-./scripts/build-all-components.sh TARGET
+./scripts/build-packages.sh TARGET
 ```
 where `TARGET` is a make target that is valid for each of the sub-components.
 
@@ -21,6 +21,8 @@ These include:
 * `ubuntu18.04-amd64`
 * `centos8-x86_64`
 
+If no `TARGET` is specified, all valid release targets are built.
+
 The packages are generated in the `dist` folder.
 
 ## Testing local changes
@@ -37,9 +39,23 @@ The [test/release](./test/release/) folder contains documentation on how the ins
 
 ## Releasing
 
-A utility script [`scripts/release.sh`](./scripts/release.sh) is provided to build
-packages required for release. If run without arguments, all supported distribution-architecture combinations are built. A specific distribution-architecture pair can also be provided
-```sh
-./scripts/release.sh ubuntu18.04-amd64
+In order to release packages required for a release, a utility script
+[`scripts/release-packages.sh`](./scripts/release-packages.sh) is provided.
+This script can be executed as follows:
+
+```bash
+GPG_LOCAL_USER="GPG_USER" \
+MASTER_KEY_PATH=/path/to/gpg-master.key \
+SUB_KEY_PATH=/path/to/gpg-subkey.key \
+    ./scripts/release-packages.sh REPO PACKAGE_REPO_ROOT [REFERENCE]
 ```
-where the `amd64` builds for `ubuntu18.04` are provided as an example.
+
+Where `REPO` is one of `stable` or `experimental`, `PACKAGE_REPO_ROOT` is the local path to the `libnvidia-container` repository checked out to the `gh-pages` branch, and `REFERENCE` is the git SHA that is to be released. If reference is not specified `HEAD` is assumed.
+
+This scripts performs the following basic functions:
+* Pulls the package image defined by the `REFERENCE` git SHA from the staging registry,
+* Copies the required packages to the package repository at `PACKAGE_REPO_ROOT/REPO`,
+* Signs the packages using the specified GPG keys
+
+While the last two are performed, commits are added to the package repository. These can be pushed to the relevant repository.
+
diff --git a/scripts/Dockerfile.sign.deb b/scripts/Dockerfile.sign.deb
new file mode 100644
index 00000000..ba117354
--- /dev/null
+++ b/scripts/Dockerfile.sign.deb
@@ -0,0 +1,4 @@
+FROM ubuntu:18.04
+
+RUN apt-get update && \
+    apt-get install -y apt-utils gpg xz-utils
diff --git a/scripts/Dockerfile.sign.rpm b/scripts/Dockerfile.sign.rpm
new file mode 100644
index 00000000..900cd305
--- /dev/null
+++ b/scripts/Dockerfile.sign.rpm
@@ -0,0 +1,3 @@
+FROM centos:8
+
+RUN yum install -y createrepo rpm-sign pinentry
diff --git a/scripts/release.sh b/scripts/build-packages.sh
similarity index 100%
rename from scripts/release.sh
rename to scripts/build-packages.sh
diff --git a/scripts/get-component-versions.sh b/scripts/get-component-versions.sh
index b14e092e..80e34a51 100755
--- a/scripts/get-component-versions.sh
+++ b/scripts/get-component-versions.sh
@@ -54,12 +54,16 @@ nvidia_docker_tag=${nvidia_container_toolkit_tag}
 nvidia_docker_version_tag="${nvidia_docker_version}${nvidia_docker_tag:+~${nvidia_docker_tag}}"
 
 echo "LIBNVIDIA_CONTAINER_VERSION=${libnvidia_container_version_tag}"
+echo "LIBNVIDIA_CONTAINER_PACKAGE_VERSION=${libnvidia_container_version_tag//\~/-}"
 echo "NVIDIA_CONTAINER_TOOLKIT_VERSION=${nvidia_container_toolkit_version}"
 echo "NVIDIA_CONTAINER_TOOLKIT_TAG=${nvidia_container_toolkit_tag}"
+echo "NVIDIA_CONTAINER_TOOLKIT_PACKAGE_VERSION=${nvidia_container_toolkit_version_tag//\~/-}"
 if [[ "${libnvidia_container_version_tag}" != "${nvidia_container_toolkit_version_tag}" ]]; then
     >&2 echo "WARNING: The libnvidia-container and nvidia-container-toolkit versions do not match"
 fi
 echo "NVIDIA_CONTAINER_RUNTIME_VERSION=${nvidia_container_runtime_version}"
 echo "NVIDIA_CONTAINER_RUNTIME_TAG=${nvidia_container_runtime_tag}"
+echo "NVIDIA_CONTAINER_RUNTIME_PACKAGE_VERSION=${nvidia_container_runtime_version_tag//\~/-}"
 echo "NVIDIA_DOCKER_VERSION=${nvidia_docker_version}"
 echo "NVIDIA_DOCKER_TAG=${nvidia_docker_tag}"
+echo "NVIDIA_DOCKER_PACKAGE_VERSION=${nvidia_docker_version_tag//\~/-}"
diff --git a/scripts/packages-sign-all.sh b/scripts/packages-sign-all.sh
new file mode 100755
index 00000000..1b660feb
--- /dev/null
+++ b/scripts/packages-sign-all.sh
@@ -0,0 +1,95 @@
+#!/usr/bin/env bash
+
+: "${ALL_DEBS:? Must set ALL_DEBS}"
+: "${ALL_RPMS:? Must set ALL_RPMS}"
+: "${GPG_LOCAL_USER:? Must set GPG_LOCAL_USER}"
+: "${TARGETS:? Must set TARGETS}"
+
+set -x -e
+
+function deb-sign {
+	local last_found
+	for r in ${*}; do
+		if [ -f "./${r}" ]; then
+			last_found=${r}
+		fi
+	done
+	if [[ -z ${last_found} ]]; then
+		echo "WARNING: No expected package found in $(pwd); skipping signing of repo;"
+		return
+	fi
+	apt-ftparchive packages . \
+		| tee Packages \
+		| xz > Packages.xz
+	apt-ftparchive -c repo.conf release . \
+		| gpg --batch --yes --expert --clearsign \
+			--armor \
+			--no-emit-version \
+			--no-comments \
+			--personal-digest-preferences sha512 \
+			--local-user ${GPG_LOCAL_USER} \
+		> InRelease
+}
+
+function rpm-sign {
+	for r in ${*}; do
+		if [ -f "./${r}" ]; then
+			rpmsign --addsign --key-id A04EA552 --digest-algo=sha512 "${r}"
+		fi
+	done
+	createrepo -v --no-database -s sha512 --compress-type xz --revision "1.0" .
+	gpg2 --batch --yes --expert --sign --detach-sign \
+		--armor \
+		--no-emit-version \
+		--no-comments --personal-digest-preferences sha512 \
+		--local-user ${GPG_LOCAL_USER} \
+	repodata/repomd.xml
+}
+
+function sign() {
+	local target=$1
+    local dst_root=$2
+
+	local src_dist=${target%-*}
+    local dist=${src_dist/amazonlinux/amzn}
+
+	local pkg_type
+    case ${src_dist} in
+    amazonlinux*) pkg_type=rpm
+        ;;
+    centos*) pkg_type=rpm
+        ;;
+    debian*) pkg_type=deb
+        ;;
+    opensuse-leap*) pkg_type=rpm
+        ;;
+    ubuntu*) pkg_type=deb
+        ;;
+    *) echo "ERROR: unexpected distribution ${src_dist}"
+        ;;
+    esac
+
+    local arch=${target##*-}
+    case ${src_dist} in
+    ubuntu*) arch=${arch//ppc64le/ppc64el}
+    esac
+
+    local dst=${dst_root}/${dist}/${arch}
+
+	if [[ ! -d ${dst} ]]; then
+		echo "Directory ${dst} not found. Skipping"
+		return
+	fi
+
+	cd ${dst}
+	if [[ -f "/etc/debian_version" ]]; then
+		[[ ${pkg_type} == "deb" ]] && deb-sign ${ALL_DEBS}
+	else
+		[[ ${pkg_type} == "rpm" ]] && rpm-sign ${ALL_RPMS}
+	fi
+	cd -
+}
+
+for target in ${TARGETS[@]}; do
+    sign ${target} $(pwd)
+done
diff --git a/scripts/release-packages.sh b/scripts/release-packages.sh
new file mode 100755
index 00000000..88bce898
--- /dev/null
+++ b/scripts/release-packages.sh
@@ -0,0 +1,233 @@
+#!/usr/bin/env bash
+
+# Copyright (c) 2021, NVIDIA CORPORATION.  All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+function assert_usage() {
+    echo "Incorrect arguments: $*"
+    echo "$(basename ${BASH_SOURCE[0]}) PACKAGE_REPO_ROOT [SHA]"
+    echo "\tPACKAGE_REPO_ROOT: The path to the libnvidia-container repository"
+    echo "\tSHA: The SHA / reference to release. [Default: HEAD]"
+    exit 1
+}
+
+set -e
+
+SCRIPTS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/../scripts && pwd )"
+PROJECT_ROOT="$( cd ${SCRIPTS_DIR}/.. && pwd )"
+
+if [[ $# -lt 1 || $# -gt 2 ]]; then
+    assert_usage $*
+fi
+
+PACKAGE_REPO_ROOT=$1
+if [[ ! -d ${PACKAGE_REPO_ROOT} ]]; then
+    echo "The specified PACKAGE_REPO_ROOT '${PACKAGE_REPO_ROOT}' must exist"
+    exit 1
+fi
+
+: ${REFERENCE:="HEAD"}
+if [[ $# -ge 2 ]]; then
+    REFERENCE=$2
+fi
+
+eval $(${SCRIPTS_DIR}/get-component-versions.sh)
+
+TAG=v"${NVIDIA_CONTAINER_TOOLKIT_PACKAGE_VERSION}"
+SHA=$(git rev-parse --short=8 ${REFERENCE})
+
+REPO="experimental"
+if [[ ${TAG/rc./} == ${TAG} ]]; then
+    REPO="stable"
+fi
+
+PACKAGE_CACHE=release-${TAG}-${REPO}
+
+echo "Fetching packages with SHA '${SHA}' as tag '${TAG}' to ${PACKAGE_CACHE}"
+IMAGE_NAME="registry.gitlab.com/nvidia/container-toolkit/container-toolkit/staging/container-toolkit"
+IMAGE_TAG=${SHA}-packaging
+${SCRIPTS_DIR}/pull-packages.sh \
+    ${IMAGE_NAME}:${IMAGE_TAG} \
+    ${PACKAGE_CACHE}
+
+: ${ALL_RPMS:="$(find ${PACKAGE_CACHE} -name "*.rpm" -exec basename {} \; | sort | uniq)"}
+: ${ALL_DEBS:="$(find ${PACKAGE_CACHE} -name "*.deb" -exec basename {} \; | sort | uniq)"}
+
+
+PACKAGE_REPO_ROOT=$(cd "${PACKAGE_REPO_ROOT}" && pwd)
+echo "Updating ${REPO} repo at ${PACKAGE_REPO_ROOT}"
+
+docker build \
+    -t nvidia/toolkit-deb-pkg-signer \
+    -f ${SCRIPTS_DIR}/Dockerfile.sign.deb \
+        ${SCRIPTS_DIR}
+
+docker build \
+    -t nvidia/toolkit-rpm-pkg-signer \
+    -f ${SCRIPTS_DIR}/Dockerfile.sign.rpm \
+        ${SCRIPTS_DIR}
+
+function sync() {
+    local target=$1
+    local src_root=$2
+    local dst_root=$3
+
+    local src_dist=${target%-*}
+    local dst_dist=${src_dist/amazonlinux/amzn}
+
+    local pkg_type
+    case ${src_dist} in
+    amazonlinux*) pkg_type=rpm
+        ;;
+    centos*) pkg_type=rpm
+        ;;
+    debian*) pkg_type=deb
+        ;;
+    opensuse-leap*) pkg_type=rpm
+        ;;
+    ubuntu*) pkg_type=deb
+        ;;
+    *) echo "ERROR: unexpected distribution ${src_dist}"
+        ;;
+    esac
+
+    local arch=${target##*-}
+    local dst_arch=${arch}
+    case ${src_dist} in
+    ubuntu*) dst_arch=${arch//ppc64le/ppc64el}
+    esac
+
+    local src=${src_root}/${src_dist}/${arch}
+    local dst=${dst_root}/${dst_dist}/${dst_arch}
+
+    if [[ ! -d ${src} || -z $(ls ${src}/*.${pkg_type}) ]]; then
+        echo "Skipping ${src}"
+        return
+    fi
+    mkdir -p ${dst}
+    cp ${src}/libnvidia-container*.${pkg_type} ${dst}
+    cp ${src}/nvidia-container-toolkit*.${pkg_type} ${dst}
+    if [[ ${REPO} == "stable" ]]; then
+        cp ${src}/nvidia-container-runtime*.${pkg_type} ${dst}
+        cp ${src}/nvidia-docker*.${pkg_type} ${dst}
+    fi
+}
+
+# This list represents the distribution-architecture pairs that are actually published
+# to the relevant repositories. This targets forwarded to the build-all-components script
+# can be overridden by specifying command line arguments.
+all=(
+    amazonlinux2-aarch64
+    amazonlinux2-x86_64
+    centos7-ppc64le
+    centos7-x86_64
+    centos8-aarch64
+    centos8-ppc64le
+    centos8-x86_64
+    debian10-amd64
+    debian9-amd64
+    opensuse-leap15.1-x86_64
+    ubuntu16.04-amd64
+    ubuntu16.04-ppc64le
+    ubuntu18.04-amd64
+    ubuntu18.04-arm64
+    ubuntu18.04-ppc64le
+)
+
+targets=${all[@]}
+
+_current_branch=$(git -C ${PACKAGE_REPO_ROOT} rev-parse --abbrev-ref HEAD)
+if [[ x"${_current_branch}" != x"gh-pages" ]]; then
+    echo "It is expected that the gh-pages branch be checked out"
+    exit 1
+fi
+
+: ${UPSTREAM_REMOTE:="origin"}
+_remote_name=$( git remote -v | grep "git@gitlab.com:nvidia/container-toolkit/libnvidia-container.git (push)" | cut -d$'\t' -f1 )
+if [[ x"${_remote_name}" != x"${UPSTREAM_REMOTE}" ]]; then
+    echo "Identified ${_remote_name} as git@gitlab.com:nvidia/container-toolkit/libnvidia-container.git remote."
+    echo "Set UPSTREAM_REMOTE=${_remote_name} instead of ${UPSTREAM_REMOTE}"
+fi
+
+: ${UPSTREAM_REFERENCE:="${UPSTREAM_REMOTE}/gh-pages"}
+git -C ${PACKAGE_REPO_ROOT} reset --hard ${UPSTREAM_REFERENCE}
+git -C ${PACKAGE_REPO_ROOT} clean -fdx ${REPO}
+
+for target in ${targets[@]}; do
+    sync ${target} ${PACKAGE_CACHE} ${PACKAGE_REPO_ROOT}/${REPO}
+done
+
+git -C ${PACKAGE_REPO_ROOT} add ${REPO}
+
+if [[ ${REPO} == "stable" ]]; then
+# Stable release
+git -C ${PACKAGE_REPO_ROOT} commit -s -F- <<EOF
+Add packages for NVIDIA Container Toolkit ${TAG} release
+
+These include:
+* libnvidia-container* ${LIBNVIDIA_CONTAINER_PACKAGE_VERSION}
+* nvidia-container-toolkit ${NVIDIA_CONTAINER_TOOLKIT_PACKAGE_VERSION}
+* nvidia-container-runtime ${NVIDIA_CONTAINER_RUNTIME_PACKAGE_VERSION}
+* nvidia-docker ${NVIDIA_DOCKER_PACKAGE_VERSION}
+EOF
+else
+# Experimental / release candidate release
+git -C ${PACKAGE_REPO_ROOT} commit -s -F- <<EOF
+Add packages for NVIDIA Container Toolkit ${TAG} ${REPO} release
+
+These include:
+* libnvidia-container* ${LIBNVIDIA_CONTAINER_PACKAGE_VERSION}
+* nvidia-container-toolkit ${NVIDIA_CONTAINER_TOOLKIT_PACKAGE_VERSION}
+EOF
+fi
+
+: ${MASTER_KEY_PATH:? Path to master key MASTER_KEY_PATH must be set}
+: ${SUB_KEY_PATH:? Path to sub key SUB_KEY_PATH must be set}
+: ${GPG_LOCAL_USER:? GPG_LOCAL_USER must be set}
+: ${GNUPG_CONF:=$(mktemp -d -t nvidia-container-toolkit-package-XXXXXXXXXX)}
+
+function sign() {
+    local pkg_type=$1
+    docker run -it --rm \
+        -e ALL_DEBS="${ALL_DEBS}" \
+        -e ALL_RPMS="${ALL_RPMS}" \
+        -e GPG_LOCAL_USER="${GPG_LOCAL_USER}" \
+        -e TARGETS="${targets}" \
+        -v ${PACKAGE_REPO_ROOT}/${REPO}:/sign-packages \
+        -v ${MASTER_KEY_PATH}:/keys/master.key:ro \
+        -v ${SUB_KEY_PATH}:/keys/sub.key:ro \
+        -v ${SCRIPTS_DIR}:/helpers \
+        -w /sign-packages \
+            nvidia/toolkit-${pkg_type}-pkg-signer \
+        bash -x -c "
+        export GPG_TTY=\$(tty);
+        gpg --import /keys/master.key;
+        gpg --import /keys/sub.key;
+        /helpers/packages-sign-all.sh;
+        "
+
+}
+
+sign deb
+
+git -C ${PACKAGE_REPO_ROOT} add ${REPO}
+git -C ${PACKAGE_REPO_ROOT} commit -s -m "TOFIX: Sign deb packages for ${TAG} in ${REPO}"
+
+sign rpm
+
+git -C ${PACKAGE_REPO_ROOT} add ${REPO}
+git -C ${PACKAGE_REPO_ROOT} commit -s -m "TOFIX: Sign rpm packages for ${TAG} in ${REPO}"
+
+echo "To publish changes, go to ${PACKAGE_REPO_ROOT} and run: "
+echo "   git rebase -i ${UPSTREAM_REFERENCE}"
diff --git a/test/release/Makefile b/test/release/Makefile
index 3436bf69..37cbee25 100644
--- a/test/release/Makefile
+++ b/test/release/Makefile
@@ -56,4 +56,4 @@ $(RUN_TARGETS): run-%: image-%
 
 # Ensure that the local package root exists
 $(RELEASE_TARGETS): release-%: $(LOCAL_PACKAGE_ROOT)/$(*)/$(ARCH)
-	$(PROJECT_ROOT)/scripts/release.sh $(*)-$(ARCH)
\ No newline at end of file
+	$(PROJECT_ROOT)/scripts/build-packages.sh $(*)-$(ARCH)