Skip to content

Commit c0849b6

Browse files
feat(node): official kth node Docker image + GHCR publish workflow (#2)
* feat(node): add kth full node image and GHCR publish workflow Adds the first official Docker image of the kth node, alongside the existing base/ and gcc/ toolchain images. node/Dockerfile builds FROM the kthnode/gcc15-ubuntu24.04 toolchain and installs kth via 'conan install --requires=kth/<version> --deployer=direct_deploy --build=missing'. The kth package and its heavy dependencies are pulled prebuilt from packages.kth.cash; only pieces without a matching prebuilt for this profile (currently utxoz) are compiled, ~1-2 min. The runtime stage is a slim ubuntu:24.04 (~103 MB) carrying only the GCC 15 C++ runtime, the CA bundle and the kth binary, so it needs no package manager. build-node.yml is a reusable (workflow_call) + manually dispatchable workflow that builds node/ for a given version and pushes ghcr.io/<owner>/kth:<version> and :latest, authenticating with the built-in GITHUB_TOKEN (no secrets required). * fix(node): run as non-root and harden the publish workflow Addresses review feedback: - Dockerfile: add an unprivileged 'kth' user, own /data, and USER kth before the entrypoint instead of running the node as root. - build-node.yml: validate the version input against a strict pattern via an env var (avoids shell injection from workflow inputs) and set persist-credentials: false on checkout. Action refs and the toolchain tag are kept as-is for consistency with the existing knuth.yml workflow and the repo's :latest toolchain convention. * fix(ci): reject version build-metadata that is not a valid Docker tag Drop '+' from the accepted version pattern: Docker tags allow only [A-Za-z0-9_.-], so a value like 1.0.0+meta would pass validation but fail at push time. Reject it up front instead. * rename node/ → kth/ and align action versions - Move node/Dockerfile to kth/Dockerfile so the directory name matches the published image (ghcr.io/k-nuth/kth) and the project naming convention elsewhere in the org. - Update build context in build-node.yml accordingly. - Bump pinned action versions: actions/checkout v4 → v7 docker/setup-buildx-action v3 → v4 docker/login-action v3 → v4 docker/build-push-action v6 → v7 * address remaining CodeRabbit comments on the node image - Tighten the version regex: only `-` may introduce a prerelease suffix. `1.2.3.rc1` (dot-separated) used to slip past the guard and fail later in conan; now it's rejected up front. - Skip the `:latest` tag for prereleases. An `-rc.N` build no longer displaces the stable `:latest` — only X.Y.Z without a `-` suffix publishes `:latest`. - Pin the toolchain image by sha256 digest instead of `:latest`. A future retag of `gcc15-ubuntu24.04:latest` would otherwise silently change the binaries we produce. Bump the digest deliberately when the toolchain moves. - Fix a stale comment that still pointed at `node/` after the rename to `kth/`. --------- Co-authored-by: CyberAshven <cyberashven@users.noreply.github.com> Co-authored-by: Fernando Pelliccioni <fpelliccioni@gmail.com>
1 parent 6bd66e8 commit c0849b6

2 files changed

Lines changed: 170 additions & 0 deletions

File tree

.github/workflows/build-node.yml

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
name: Build node image
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
version:
7+
description: kth version to build (e.g. v1.0.0 or 1.0.0)
8+
required: true
9+
type: string
10+
workflow_dispatch:
11+
inputs:
12+
version:
13+
description: kth version to build (e.g. v1.0.0 or 1.0.0)
14+
required: true
15+
type: string
16+
17+
permissions:
18+
contents: read
19+
packages: write
20+
21+
jobs:
22+
build-node-docker-image:
23+
runs-on: ubuntu-latest
24+
steps:
25+
# Explicit repository/ref: when this reusable workflow is invoked from
26+
# k-nuth/kth, the default checkout would be the caller (kth); we need this
27+
# repo's kth/ build context instead.
28+
- uses: actions/checkout@v7
29+
with:
30+
repository: k-nuth/docker-images
31+
ref: master
32+
persist-credentials: false
33+
34+
- id: meta
35+
env:
36+
INPUT_VERSION: ${{ inputs.version }}
37+
REPO_OWNER: ${{ github.repository_owner }}
38+
run: |
39+
version="${INPUT_VERSION#v}"
40+
# SemVer-ish: X.Y.Z with optional `-prerelease` suffix.
41+
# `-` is the only legal separator for the prerelease segment;
42+
# `1.2.3.rc1` (dot-separated) would slip into conan as a real
43+
# version and fail later.
44+
if ! [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then
45+
echo "::error::Invalid version '$INPUT_VERSION' (expected e.g. v1.0.0 or v1.0.0-rc.1)"
46+
exit 1
47+
fi
48+
owner="$(echo "$REPO_OWNER" | tr '[:upper:]' '[:lower:]')"
49+
# Stable releases also update the `:latest` tag. Pre-releases
50+
# (anything with a `-` suffix) tag only the version itself so
51+
# an `-rc` build doesn't displace the most recent stable.
52+
if [[ "$version" == *-* ]]; then
53+
tags=":${version}"
54+
else
55+
tags=":${version},:latest"
56+
fi
57+
{
58+
echo "version=${version}"
59+
echo "image=ghcr.io/${owner}/kth"
60+
echo "tags=${tags}"
61+
} >> "$GITHUB_OUTPUT"
62+
63+
- uses: docker/setup-buildx-action@v4
64+
65+
- uses: docker/login-action@v4
66+
with:
67+
registry: ghcr.io
68+
username: ${{ github.actor }}
69+
password: ${{ secrets.GITHUB_TOKEN }}
70+
71+
- id: tags
72+
env:
73+
IMAGE: ${{ steps.meta.outputs.image }}
74+
SUFFIXES: ${{ steps.meta.outputs.tags }}
75+
run: |
76+
# tags = ":version" or ":version,:latest" — expand to full refs
77+
out=""
78+
IFS=',' read -ra parts <<< "$SUFFIXES"
79+
for s in "${parts[@]}"; do
80+
out+="${IMAGE}${s}"$'\n'
81+
done
82+
{
83+
echo "list<<EOF"
84+
echo "${out}"
85+
echo "EOF"
86+
} >> "$GITHUB_OUTPUT"
87+
88+
- uses: docker/build-push-action@v7
89+
with:
90+
context: ./kth
91+
push: true
92+
build-args: |
93+
KNUTH_VERSION=${{ steps.meta.outputs.version }}
94+
tags: ${{ steps.tags.outputs.list }}
95+
labels: |
96+
org.opencontainers.image.source=https://github.com/k-nuth/kth
97+
org.opencontainers.image.version=${{ steps.meta.outputs.version }}

kth/Dockerfile

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# syntax=docker/dockerfile:1
2+
#
3+
# Knuth (kth) Bitcoin Cash full node.
4+
#
5+
# The build stage reuses the project's own toolchain image so the compile
6+
# profile matches the binaries published on packages.kth.cash. The kth package
7+
# and all of its heavy dependencies (boost, openssl, lmdb, gmp, ...) are pulled
8+
# prebuilt from that remote; only the few pieces that have no matching prebuilt
9+
# for this profile (currently utxoz) are compiled here, which takes ~1-2 min.
10+
11+
# Pinned by digest so a future `:latest` retag of the toolchain image
12+
# doesn't silently change the binaries we produce. Bump deliberately when
13+
# the toolchain moves (GCC bump, base distro bump, ...).
14+
ARG TOOLCHAIN=kthnode/gcc15-ubuntu24.04@sha256:4988fdeb3654c2b5ea591ffa4454f67e1a631549449c62e35060ba69da79eb67
15+
ARG RUNTIME=ubuntu:24.04
16+
17+
FROM ${TOOLCHAIN} AS build
18+
19+
# kth release version without the leading "v" (e.g. 1.0.0).
20+
ARG KNUTH_VERSION
21+
ARG CURRENCY=BCH
22+
23+
RUN test -n "${KNUTH_VERSION}" || { echo "KNUTH_VERSION build-arg is required"; exit 1; }
24+
25+
# kthbuild is required to load the kth Conan recipe.
26+
RUN pip install --no-cache-dir --upgrade "kthbuild>=4,<5"
27+
28+
RUN conan profile detect --force \
29+
&& conan remote add kth https://packages.kth.cash/api/ --force \
30+
&& conan install --requires=kth/${KNUTH_VERSION} \
31+
--deployer=direct_deploy --output-folder=/deploy --build=missing \
32+
-o "kth/*:currency=${CURRENCY}" \
33+
-o "kth/*:console=True" \
34+
-o "utxoz/*:with_tests=False" \
35+
-o "utxoz/*:with_benchmarks=False" \
36+
-o "utxoz/*:with_large_benchmarks=False" \
37+
-s compiler.cppstd=23
38+
39+
40+
FROM ${RUNTIME} AS runtime
41+
42+
LABEL org.opencontainers.image.title="Knuth (kth) node" \
43+
org.opencontainers.image.description="Knuth Bitcoin Cash full node" \
44+
org.opencontainers.image.source="https://github.com/k-nuth/kth" \
45+
org.opencontainers.image.url="https://kth.cash" \
46+
org.opencontainers.image.licenses="MIT"
47+
48+
# The binary is compiled with GCC 15; ship its matching C++ runtime so the image
49+
# does not depend on the base distro's (older) libstdc++ / libgcc ABI. The CA
50+
# bundle is copied too, so the runtime stage needs no package manager at all.
51+
COPY --from=build /usr/local/lib64/libstdc++.so.6* /opt/kth/lib/
52+
COPY --from=build /usr/local/lib64/libgcc_s.so.1* /opt/kth/lib/
53+
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
54+
RUN echo /opt/kth/lib > /etc/ld.so.conf.d/kth.conf && ldconfig
55+
56+
COPY --from=build /deploy/direct_deploy/kth/bin/kth /usr/local/bin/kth
57+
58+
# Run the node as an unprivileged user. /data is owned by it so the node can
59+
# persist its state there (and named volumes inherit that ownership).
60+
RUN useradd --system --user-group --no-create-home --home-dir /data kth \
61+
&& mkdir -p /data \
62+
&& chown kth:kth /data
63+
USER kth
64+
65+
# kth writes its data (blockchain/, archive/, hosts.cache, logs) relative to the
66+
# working directory, so anchor it to a single mountable location.
67+
WORKDIR /data
68+
VOLUME ["/data"]
69+
70+
# P2P port (mainnet). Other ports depend on the chosen network / configuration.
71+
EXPOSE 8333
72+
73+
ENTRYPOINT ["kth"]

0 commit comments

Comments
 (0)