├── .dockerignore ├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ ├── conventional-commits.yml │ ├── ci-docker.yml │ └── publish.yml ├── bin ├── run-client ├── entrypoint ├── run-network └── run-node ├── LICENSE ├── Dockerfile └── README.md /.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | README.md 3 | .github/ 4 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Blink Labs 2 | # 3 | * @blinklabs-io/core @blinklabs-io/ops 4 | *.md @blinklabs-io/core @blinklabs-io/docs @blinklabs-io/pms 5 | LICENSE @blinklabs-io/core @blinklabs-io/pms 6 | -------------------------------------------------------------------------------- /bin/run-client: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | [[ -n ${DEBUG} ]] && set -x 4 | 5 | if [[ ${1} == cli ]]; then 6 | shift 7 | fi 8 | if [[ -n ${NETWORK} ]]; then 9 | export CARDANO_NODE_SOCKET_PATH=${CARDANO_NODE_SOCKET_PATH:-/ipc/node.socket} 10 | else 11 | export CARDANO_NODE_SOCKET_PATH=${CARDANO_NODE_SOCKET_PATH:-/opt/cardano/ipc/socket} 12 | fi 13 | /usr/local/bin/cardano-cli ${@} 14 | -------------------------------------------------------------------------------- /bin/entrypoint: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | [[ -n ${DEBUG} ]] && set -x 4 | 5 | set -e 6 | 7 | if [[ -n ${NETWORK} ]]; then 8 | if [[ ${1} == cli ]]; then 9 | exec /usr/local/bin/run-client ${@} 10 | else 11 | exec /usr/local/bin/run-network ${@} 12 | fi 13 | fi 14 | 15 | case ${1} in 16 | run) exec /usr/local/bin/run-node ${@} ;; 17 | cli) exec /usr/local/bin/run-client ${@} ;; 18 | *) echo "Nothing to do! Perhaps try [run|cli], or set NETWORK environment variable."; exit 1 ;; 19 | esac 20 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "docker" 9 | directory: "/" 10 | schedule: 11 | interval: "weekly" 12 | - package-ecosystem: "github-actions" 13 | directory: "/" 14 | schedule: 15 | interval: "weekly" 16 | -------------------------------------------------------------------------------- /.github/workflows/conventional-commits.yml: -------------------------------------------------------------------------------- 1 | # The below is pulled from upstream and slightly modified 2 | # https://github.com/webiny/action-conventional-commits/blob/master/README.md#usage 3 | 4 | name: Conventional Commits 5 | 6 | on: 7 | pull_request: 8 | 9 | jobs: 10 | build: 11 | name: Conventional Commits 12 | runs-on: [self-hosted, Linux, X64, ansible] 13 | permissions: 14 | contents: read 15 | steps: 16 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 https://github.com/actions/checkout/releases/tag/v6.0.1 17 | - uses: webiny/action-conventional-commits@8bc41ff4e7d423d56fa4905f6ff79209a78776c7 # v1.3.0 https://github.com/webiny/action-conventional-commits/releases/tag/v1.3.0 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 CloudStruct, LLC. 4 | Copyright (c) 2023 Blink Labs Software 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /.github/workflows/ci-docker.yml: -------------------------------------------------------------------------------- 1 | name: Docker CI 2 | 3 | on: 4 | pull_request: 5 | branches: ["main", "release/**"] 6 | paths: 7 | ["Dockerfile", "bin/**", "config/**", ".github/workflows/ci-docker.yml"] 8 | 9 | env: 10 | REGISTRY: ghcr.io 11 | IMAGE_NAME: blinklabs/cardano-node 12 | 13 | permissions: 14 | contents: read 15 | 16 | jobs: 17 | build: 18 | strategy: 19 | matrix: 20 | arch: [amd64, arm64] 21 | runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }} 22 | steps: 23 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 https://github.com/actions/checkout/releases/tag/v6.0.1 24 | 25 | - uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 https://github.com/docker/setup-buildx-action/releases/tag/v3.11.1 26 | 27 | - id: meta 28 | uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0 https://github.com/docker/metadata-action/releases/tag/v5.10.0 29 | with: 30 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 31 | flavor: | 32 | latest=false 33 | suffix=-${{ matrix.arch == 'arm64' && 'arm64v8' || 'amd64' }} 34 | 35 | - name: Build Docker image 36 | uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 https://github.com/docker/build-push-action/releases/tag/v6.18.0 37 | with: 38 | context: . 39 | push: false 40 | tags: ${{ steps.meta.outputs.tags }} 41 | labels: ${{ steps.meta.outputs.labels }} 42 | cache-from: type=gha,scope=buildkit-${{ matrix.arch }} 43 | cache-to: type=gha,mode=max,scope=buildkit-${{ matrix.arch }} 44 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ghcr.io/blinklabs-io/haskell:9.6.6-3.12.1.0-3 AS cardano-node-build 2 | # Install cardano-node 3 | ARG NODE_VERSION=10.5.3 4 | ENV NODE_VERSION=${NODE_VERSION} 5 | RUN echo "Building tags/${NODE_VERSION}..." \ 6 | && echo tags/${NODE_VERSION} > /CARDANO_BRANCH \ 7 | && git clone https://github.com/input-output-hk/cardano-node.git \ 8 | && cd cardano-node \ 9 | && git fetch --all --recurse-submodules --tags \ 10 | && git tag \ 11 | && git checkout tags/${NODE_VERSION} \ 12 | && echo "with-compiler: ghc-${GHC_VERSION}" >> cabal.project.local \ 13 | && echo "tests: False" >> cabal.project.local \ 14 | && cabal update \ 15 | && cabal build all \ 16 | && mkdir -p /root/.local/bin/ \ 17 | && cp -p "$(./scripts/bin-path.sh cardano-node)" /root/.local/bin/ \ 18 | && cp -p "$(./scripts/bin-path.sh cardano-tracer)" /root/.local/bin/ \ 19 | && rm -rf /root/.cabal/packages \ 20 | && rm -rf /usr/local/lib/ghc-${GHC_VERSION}/ /usr/local/share/doc/ghc-${GHC_VERSION}/ \ 21 | && rm -rf /code/cardano-node/dist-newstyle/ \ 22 | && rm -rf /root/.cabal/store/ghc-${GHC_VERSION} 23 | 24 | FROM ghcr.io/blinklabs-io/cardano-cli:10.13.1.0-1 AS cardano-cli 25 | FROM ghcr.io/blinklabs-io/cardano-configs:20251128-1 AS cardano-configs 26 | FROM ghcr.io/blinklabs-io/mithril-client:0.12.33-1 AS mithril-client 27 | FROM ghcr.io/blinklabs-io/mithril-signer:0.2.276-1 AS mithril-signer 28 | FROM ghcr.io/blinklabs-io/nview:0.13.0 AS nview 29 | FROM ghcr.io/blinklabs-io/txtop:0.14.0 AS txtop 30 | 31 | FROM debian:bookworm-slim AS cardano-node 32 | ENV LD_LIBRARY_PATH="/usr/local/lib:$LD_LIBRARY_PATH" 33 | ENV PKG_CONFIG_PATH="/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH" 34 | COPY --from=cardano-node-build /usr/local/lib/ /usr/local/lib/ 35 | COPY --from=cardano-node-build /usr/local/include/ /usr/local/include/ 36 | COPY --from=cardano-node-build /root/.local/bin/cardano-* /usr/local/bin/ 37 | COPY --from=cardano-configs /config/ /opt/cardano/config/ 38 | COPY --from=cardano-cli /usr/local/bin/cardano-cli /usr/local/bin/ 39 | COPY --from=mithril-client /bin/mithril-client /usr/local/bin/ 40 | COPY --from=mithril-signer /bin/mithril-signer /usr/local/bin/ 41 | COPY --from=nview /bin/nview /usr/local/bin/ 42 | COPY --from=txtop /bin/txtop /usr/local/bin/ 43 | COPY bin/ /usr/local/bin/ 44 | RUN apt-get update -y && \ 45 | apt-get install -y \ 46 | bc \ 47 | curl \ 48 | iproute2 \ 49 | jq \ 50 | libffi8 \ 51 | libgmp10 \ 52 | liblmdb0 \ 53 | libncursesw5 \ 54 | libnuma1 \ 55 | libsystemd0 \ 56 | libssl3 \ 57 | libtinfo6 \ 58 | llvm-14-runtime \ 59 | lsof \ 60 | netbase \ 61 | pkg-config \ 62 | procps \ 63 | socat \ 64 | sqlite3 \ 65 | wget \ 66 | zlib1g && \ 67 | rm -rf /var/lib/apt/lists/* && \ 68 | chmod +x /usr/local/bin/* 69 | EXPOSE 3001 12788 12798 70 | ENTRYPOINT ["/usr/local/bin/entrypoint"] 71 | -------------------------------------------------------------------------------- /bin/run-network: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | [[ -n ${DEBUG} ]] && set -x 4 | 5 | if [[ -z ${NETWORK} ]]; then 6 | echo "[Error] Cannot obtain NETWORK env variable" 7 | exit 1 8 | fi 9 | 10 | export PATH=${PATH}:/usr/local/bin 11 | mkdir -p /ipc 12 | case ${NETWORK} in 13 | mainnet|preview|preprod|sanchonet) : ;; 14 | *) echo "[Error] Managed configuration for network ${NETWORK} does not exist"; exit 1 ;; 15 | esac 16 | 17 | # Mithril 18 | if ! test -e /data/db/protocolMagicId; then 19 | RESTORE_NETWORK=${RESTORE_NETWORK:-true} 20 | if [[ ${RESTORE_NETWORK} != true ]]; then 21 | __skip=true 22 | fi 23 | case ${NETWORK} in 24 | mainnet|preprod) __path=release-${NETWORK} ;; 25 | preview) __path=pre-release-${NETWORK} ;; 26 | sanchonet) __path=testing-${NETWORK} ;; 27 | *) echo "Mithril not supported on ${NETWORK}... skipping"; __skip=true ;; 28 | esac 29 | if [[ ${__skip} != true ]]; then 30 | export GENESIS_VERIFICATION_KEY=$(&1 >/dev/null 54 | if test $? -eq 0; then 55 | sed -i \ 56 | -e 's/"PeerSharing": false/"PeerSharing": true/' \ 57 | -e 's/"TraceMempool": false/"TraceMempool": true/' \ 58 | /opt/cardano/config/${NETWORK}/config.json 59 | fi 60 | set -e 61 | 62 | echo "Starting: /usr/local/bin/cardano-node run" 63 | echo "--config /opt/cardano/config/${NETWORK}/config.json" 64 | echo "--database-path /data/db" 65 | echo "--host-addr 0.0.0.0" 66 | echo "--port 3001" 67 | echo "--socket-path /ipc/node.socket" 68 | echo "--topology /opt/cardano/config/${NETWORK}/topology.json" 69 | echo "+RTS" 70 | echo "-N2" 71 | echo "-I0" 72 | echo "-A16m" 73 | echo "-qg" 74 | echo "-qb" 75 | echo "--disable-delayed-os-memory-return" 76 | echo "-RTS" 77 | echo "..or, once again, in a single line:" 78 | echo "/usr/local/bin/cardano-node run --config /opt/cardano/config/${NETWORK}/config.json --database-path /data/db --host-addr 0.0.0.0 --port 3001 --socket-path /ipc/node.socket --topology /opt/cardano/config/${NETWORK}/topology.json +RTS -N2 -I0 -A16m -qg -qb --disable-delayed-os-memory-return -RTS" 79 | exec /usr/local/bin/cardano-node run --config /opt/cardano/config/${NETWORK}/config.json --database-path /data/db --host-addr 0.0.0.0 --port 3001 --socket-path /ipc/node.socket --topology /opt/cardano/config/${NETWORK}/topology.json +RTS -N2 -I0 -A16m -qg -qb --disable-delayed-os-memory-return -RTS ${@} 80 | -------------------------------------------------------------------------------- /bin/run-node: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | [[ -n ${DEBUG} ]] && set -x 4 | 5 | set -eo pipefail 6 | 7 | echo "Running the cardano node ..." 8 | 9 | # setup options 10 | shift 11 | options=(${@}) 12 | for i in ${!options[@]}; do 13 | j=$((i+1)) 14 | k=${options[i]} 15 | v=${options[j]} 16 | found=false 17 | case ${k} in 18 | --config) CARDANO_CONFIG=${v}; found=true ;; 19 | --database-path) CARDANO_DATABASE_PATH=${v}; found=true ;; 20 | --host-addr) CARDANO_BIND_ADDR=${v}; found=true ;; 21 | --port) CARDANO_PORT=${v}; found=true ;; 22 | --shelley-kes-key) CARDANO_SHELLEY_KES_KEY=${v}; found=true ;; 23 | --shelley-operational-certificate) CARDANO_SHELLEY_OPERATIONAL_CERTIFICATE=${v}; found=true ;; 24 | --shelley-vrf-key) CARDANO_SHELLEY_VRF_KEY=${v}; found=true ;; 25 | --socket-path) CARDANO_SOCKET_PATH=${v}; found=true ;; 26 | --topology) CARDANO_TOPOLOGY=${v}; found=true ;; 27 | --start-as-non-producing-node) START_AS_NON_PRODUCING=${v}; found=true ;; 28 | esac 29 | if [[ ${found} == true ]]; then 30 | options[i]=""; 31 | options[j]=""; 32 | fi 33 | done 34 | 35 | # variables 36 | CARDANO_CONFIG_BASE=${CARDANO_CONFIG_BASE:-/opt/cardano/config} 37 | CARDANO_NETWORK=${CARDANO_NETWORK:-mainnet} 38 | # common 39 | CARDANO_BIND_ADDR=${CARDANO_BIND_ADDR:-0.0.0.0} 40 | CARDANO_BLOCK_PRODUCER=${CARDANO_BLOCK_PRODUCER:-false} 41 | CARDANO_CONFIG=${CARDANO_CONFIG:-${CARDANO_CONFIG_BASE}/${CARDANO_NETWORK}/config.json} 42 | CARDANO_DATABASE_PATH=${CARDANO_DATABASE_PATH:-/data/db} 43 | CARDANO_LOG_DIR=${CARDANO_LOG_DIR:-/opt/cardano/logs} 44 | CARDANO_PORT=${CARDANO_PORT:-3001} 45 | CARDANO_RTS_OPTS=${CARDANO_RTS_OPTS:--N2 -A64m -I0 -qg -qb --disable-delayed-os-memory-return} 46 | CARDANO_SOCKET_PATH=${CARDANO_SOCKET_PATH:-/ipc/node.socket} 47 | CARDANO_TOPOLOGY=${CARDANO_TOPOLOGY:-${CARDANO_CONFIG_BASE}/${CARDANO_NETWORK}/topology.json} 48 | START_AS_NON_PRODUCING=${START_AS_NON_PRODUCING:-false} 49 | # mithril and devnet 50 | case ${CARDANO_NETWORK} in 51 | mainnet|preprod) __path=release-${CARDANO_NETWORK} ;; 52 | preview) __path=pre-release-${CARDANO_NETWORK} ;; 53 | sanchonet) __path=testing-${CARDANO_NETWORK} ;; 54 | devnet) 55 | # For devnet, we run as a block producer 56 | CARDANO_BLOCK_PRODUCER=true 57 | CARDANO_SHELLEY_KES_KEY=${CARDANO_CONFIG_BASE}/${CARDANO_NETWORK}/keys/kes.skey 58 | CARDANO_SHELLEY_VRF_KEY=${CARDANO_CONFIG_BASE}/${CARDANO_NETWORK}/keys/vrf.skey 59 | CARDANO_SHELLEY_OPERATIONAL_CERTIFICATE=${CARDANO_CONFIG_BASE}/${CARDANO_NETWORK}/keys/opcert.cert 60 | RESTORE_SNAPSHOT=false 61 | __path=testing-${CARDANO_NETWORK} 62 | # clean up old data 63 | rm -rf ${CARDANO_DATABASE_PATH}/* 64 | # empty topology 65 | echo '{"Producers": []}' > ${CARDANO_TOPOLOGY} 66 | # network start times 67 | sed -i "s/\"startTime\": [0-9]*/\"startTime\": $(date +%s)/" \ 68 | ${CARDANO_CONFIG_BASE}/${CARDANO_NETWORK}/byron-genesis.json 69 | sed -i "s/\"systemStart\": \".*\"/\"systemStart\": \"$(date -u +%FT%TZ)\"/" \ 70 | ${CARDANO_CONFIG_BASE}/${CARDANO_NETWORK}/shelley-genesis.json 71 | # update permissions on keys 72 | find ${CARDANO_CONFIG_BASE}/${CARDANO_NETWORK}/keys/ -type f -name \*.skey | xargs chmod 0400 73 | ;; 74 | esac 75 | AGGREGATOR_ENDPOINT=${AGGREGATOR_ENDPOINT:-https://aggregator.${__path}.api.mithril.network/aggregator} 76 | GENESIS_VERIFICATION_KEY_PATH=${GENESIS_VERIFICATION_KEY_PATH:-${CARDANO_CONFIG_BASE}/${CARDANO_NETWORK}/genesis.vkey} 77 | ANCILLARY_VERIFICATION_KEY_PATH=${ANCILLARY_VERIFICATION_KEY_PATH:-${CARDANO_CONFIG_BASE}/${CARDANO_NETWORK}/ancillary.vkey} 78 | if [[ -z $GENESIS_VERIFICATION_KEY && -f $GENESIS_VERIFICATION_KEY_PATH ]]; then 79 | GENESIS_VERIFICATION_KEY=$(<${GENESIS_VERIFICATION_KEY_PATH}) 80 | fi 81 | if [[ -z $ANCILLARY_VERIFICATION_KEY && -f $ANCILLARY_VERIFICATION_KEY_PATH ]]; then 82 | ANCILLARY_VERIFICATION_KEY=$(<${ANCILLARY_VERIFICATION_KEY_PATH}) 83 | fi 84 | SNAPSHOT_DIGEST=${SNAPSHOT_DIGEST:-latest} 85 | RESTORE_SNAPSHOT=${RESTORE_SNAPSHOT:-true} 86 | 87 | echo CARDANO_BIND_ADDR=${CARDANO_BIND_ADDR} 88 | echo CARDANO_BLOCK_PRODUCER=${CARDANO_BLOCK_PRODUCER} 89 | echo CARDANO_CONFIG=${CARDANO_CONFIG} 90 | echo CARDANO_DATABASE_PATH=${CARDANO_DATABASE_PATH} 91 | echo CARDANO_LOG_DIR=${CARDANO_LOG_DIR} 92 | echo CARDANO_NETWORK=${CARDANO_NETWORK} 93 | echo CARDANO_PORT=${CARDANO_PORT} 94 | echo CARDANO_RTS_OPTS=${CARDANO_RTS_OPTS} 95 | echo CARDANO_SOCKET_PATH=${CARDANO_SOCKET_PATH} 96 | echo CARDANO_TOPOLOGY=${CARDANO_TOPOLOGY} 97 | echo START_AS_NON_PRODUCING=${START_AS_NON_PRODUCING} 98 | 99 | # block producer only 100 | if [[ ${CARDANO_BLOCK_PRODUCER} == true ]]; then 101 | CARDANO_SHELLEY_KES_KEY=${CARDANO_SHELLEY_KES_KEY:-${CARDANO_CONFIG_BASE}/keys/kes.skey} 102 | CARDANO_SHELLEY_VRF_KEY=${CARDANO_SHELLEY_VRF_KEY:-${CARDANO_CONFIG_BASE}/keys/vrf.skey} 103 | CARDANO_SHELLEY_OPERATIONAL_CERTIFICATE=${CARDANO_SHELLEY_OPERATIONAL_CERTIFICATE:-${CARDANO_CONFIG_BASE}/keys/node.cert} 104 | echo CARDANO_SHELLEY_KES_KEY=${CARDANO_SHELLEY_KES_KEY} 105 | echo CARDANO_SHELLEY_VRF_KEY=${CARDANO_SHELLEY_VRF_KEY} 106 | echo CARDANO_SHELLEY_OPERATIONAL_CERTIFICATE=${CARDANO_SHELLEY_OPERATIONAL_CERTIFICATE} 107 | else 108 | set +e 109 | # Check if our configuration is writable (may be different from permissions) 110 | touch ${CARDANO_CONFIG} 2>&1 >/dev/null 111 | if test $? -eq 0; then 112 | sed -i \ 113 | -e 's/"PeerSharing": false/"PeerSharing": true/' \ 114 | -e 's/"TraceMempool": false/"TraceMempool": true/' \ 115 | ${CARDANO_CONFIG} 116 | fi 117 | set -e 118 | fi 119 | 120 | echo AGGREGATOR_ENDPOINT=${AGGREGATOR_ENDPOINT} 121 | echo GENESIS_VERIFICATION_KEY=${GENESIS_VERIFICATION_KEY} 122 | echo ANCILLARY_VERIFICATION_KEY=${ANCILLARY_VERIFICATION_KEY} 123 | echo SNAPSHOT_DIGEST=${SNAPSHOT_DIGEST} 124 | echo RESTORE_SNAPSHOT=${RESTORE_SNAPSHOT} 125 | 126 | mkdir -p $(dirname ${CARDANO_SOCKET_PATH}) 127 | 128 | # Mithril 129 | if ! test -e ${CARDANO_DATABASE_PATH}/protocolMagicId; then 130 | echo "Detected empty ${CARDANO_DATABASE_PATH}" 131 | if [[ ${RESTORE_SNAPSHOT} == true ]]; then 132 | mkdir -p $(dirname ${CARDANO_DATABASE_PATH}) 133 | cd $(dirname ${CARDANO_DATABASE_PATH}) 134 | export AGGREGATOR_ENDPOINT GENESIS_VERIFICATION_KEY ANCILLARY_VERIFICATION_KEY SNAPSHOT_DIGEST 135 | echo "Starting: /usr/local/bin/mithril-client cardano-db download ${SNAPSHOT_DIGEST} ${ANCILLARY_VERIFICATION_KEY:+--include-ancillary}" 136 | echo "(This could take a while...)" 137 | if [[ $(cd ${CARDANO_DATABASE_PATH} 2>/dev/null; pwd -P) != $(pwd -P)/db ]]; then 138 | rm -rf db/* 139 | fi 140 | # Handle SIGTERM during initial sync 141 | trap 'kill -TERM $(pidof mithril-client)' TERM 142 | # Run mithril-client in the background so we can capture the PID and wait 143 | mithril-client cardano-db download "${SNAPSHOT_DIGEST}" ${ANCILLARY_VERIFICATION_KEY:+--include-ancillary} & 144 | _mithril_pid=$! 145 | wait $_mithril_pid || exit $? 146 | # Reset signal handler and wait again (to avoid race condition) 147 | trap - TERM 148 | wait $_mithril_pid || exit $? 149 | if [[ $(cd ${CARDANO_DATABASE_PATH}; pwd -P) != $(pwd -P)/db ]]; then 150 | mv -f db/* ${CARDANO_DATABASE_PATH}/ 151 | fi 152 | else 153 | mkdir -p ${CARDANO_DATABASE_PATH} 154 | fi 155 | elif [[ ${RESTORE_SNAPSHOT} == true ]]; then 156 | echo "Detected populated ${CARDANO_DATABASE_PATH}... skipping restore" 157 | fi 158 | 159 | cd ${CARDANO_DATABASE_PATH} 160 | 161 | if [[ ${SOCAT_PORT:-0} != 0 ]]; then 162 | echo "Port ${SOCAT_PORT} configured for socat... launching in background" 163 | nohup socat TCP-LISTEN:${SOCAT_PORT},fork UNIX-CLIENT:${CARDANO_SOCKET_PATH},ignoreeof & 164 | fi 165 | 166 | if [[ ${CARDANO_BLOCK_PRODUCER} == true ]]; then 167 | effopts=(--config ${CARDANO_CONFIG} \ 168 | --database-path ${CARDANO_DATABASE_PATH} \ 169 | --host-addr ${CARDANO_BIND_ADDR} \ 170 | --port ${CARDANO_PORT} \ 171 | --shelley-kes-key ${CARDANO_SHELLEY_KES_KEY} \ 172 | --shelley-operational-certificate ${CARDANO_SHELLEY_OPERATIONAL_CERTIFICATE} \ 173 | --shelley-vrf-key ${CARDANO_SHELLEY_VRF_KEY} \ 174 | --socket-path ${CARDANO_SOCKET_PATH} \ 175 | --topology ${CARDANO_TOPOLOGY}) 176 | # Add --start-as-non-producing-node flag if START_AS_NON_PRODUCING is true 177 | if [[ ${START_AS_NON_PRODUCING} == true ]]; then 178 | effopts+=(--start-as-non-producing-node) 179 | fi 180 | else 181 | effopts=(--config ${CARDANO_CONFIG} \ 182 | --database-path ${CARDANO_DATABASE_PATH} \ 183 | --host-addr ${CARDANO_BIND_ADDR} \ 184 | --port ${CARDANO_PORT} \ 185 | --socket-path ${CARDANO_SOCKET_PATH} \ 186 | --topology ${CARDANO_TOPOLOGY}) 187 | fi 188 | effopts+=(${options[@]}) 189 | # RTS support 190 | read -ra rtsopts <<< ${CARDANO_RTS_OPTS} 191 | effopts+=("+RTS" ${rtsopts[@]} "-RTS") 192 | echo cardano-node run ${effopts[@]} 193 | exec /usr/local/bin/cardano-node run ${effopts[@]} 194 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # docker-cardano-node 2 | 3 | Builds a Cardano full node from source on Debian. This image attempts to keep 4 | interface compatibility with 5 | [cardano-node](https://github.com/IntersectMBO/cardano-node), but may diverge 6 | slightly, particularly with any Nix-specific paths. 7 | 8 | We found that the learning curve for using Nix is too high for many in the 9 | community who need to run a full node for various reasons but don't want to 10 | get too heavy into learning a new operating system just to extend a container 11 | image. This image uses FHS paths and installs into `/usr/local` while taking 12 | advantage of multi-stage image builds to reduce final image size over the 13 | official Nix-based distribution. 14 | 15 | ## Running a Cardano Node 16 | 17 | The container image has some differing behaviors depending on how it's 18 | invoked. There is a `NETWORK` environment variable which can be used as a 19 | short cut to starting nodes on a specific named Cardano network with a default 20 | configuration. 21 | 22 | We recommend using this method for running the container if you are not 23 | familiar with running a node. 24 | 25 | For nodes on the `preprod` and `mainnet` networks, the container will start 26 | fetching the latest Mithril snapshot if an empty data directory is detected. 27 | This snapshot will be used to bootstrap the node into an operating state much 28 | more rapidly than doing a sync from the beginning of the chain. This behavior 29 | can be disabled in the advanced path below. 30 | 31 | ### Using NETWORK (Recommended) 32 | 33 | Using the `NETWORK` environment variable causes the entrypoint script to use 34 | the simplified path. The only configurable option in this path is the 35 | `NETWORK` environment variable itself. 36 | 37 | This is in keeping with the IOHK image behavior. 38 | 39 | To run a Cardano full node on preprod: 40 | 41 | ```bash 42 | docker run --detach \ 43 | --name cardano-node \ 44 | -v node-data:/data/db \ 45 | -v node-ipc:/ipc \ 46 | -e NETWORK=preprod \ 47 | -p 3001:3001 \ 48 | ghcr.io/blinklabs-io/cardano-node 49 | ``` 50 | 51 | Node logs can be followed: 52 | 53 | ```bash 54 | docker logs -f cardano-node 55 | ``` 56 | 57 | The node can be monitoring using [nview](https://github.com/blinklabs-io/nview) 58 | from within the container. We run `nview` from within the same container as 59 | our running node to get access to information about the node process. 60 | 61 | ```bash 62 | docker exec -ti cardano-node nview 63 | ``` 64 | 65 | #### Running the Cardano CLI 66 | 67 | To run a Cardano CLI command, we use the `node-ipc` volume from our node. 68 | 69 | ```bash 70 | docker run --rm -ti \ 71 | -e NETWORK=preprod \ 72 | -v node-ipc:/ipc \ 73 | ghcr.io/blinklabs-io/cardano-node cli 74 | ``` 75 | 76 | This can be added to a shell alias: 77 | 78 | ```bash 79 | alias cardano-cli="docker run --rm -ti \ 80 | -e NETWORK=preprod \ 81 | -v node-ipc:/ipc \ 82 | ghcr.io/blinklabs-io/cardano-node cli" 83 | ``` 84 | 85 | Now, you can use cardano-cli commands as you would, normally. 86 | 87 | ```bash 88 | cardano-cli query tip --network-magic 1 89 | ``` 90 | 91 | Or, for a node running with NETWORK=mainnet: 92 | 93 | ```bash 94 | cardano-cli query tip --mainnet 95 | ``` 96 | 97 | ### Using run (Recommended for SPO/Advanced) 98 | 99 | Using the `run` argument to the image causes the entrypoint script to use the 100 | fully configurable code path. Do not set the `NETWORK` environment variable. 101 | 102 | To run a Cardano full node on mainnet with minimal configuration and defaults: 103 | 104 | Cardano Node before 10.3.1: 105 | ```bash 106 | docker run --detach \ 107 | --name cardano-node \ 108 | -v node-data:/opt/cardano/data \ 109 | -v node-ipc:/opt/cardano/ipc \ 110 | -p 3001:3001 \ 111 | ghcr.io/blinklabs-io/cardano-node run 112 | ``` 113 | 114 | **NOTE** The container paths for persist disks are different in this mode 115 | 116 | Cardano Node 10.3.1 and above: 117 | ```bash 118 | docker run --detach \ 119 | --name cardano-node \ 120 | -v node-data:/data/db \ 121 | -v node-ipc:/ipc \ 122 | -p 3001:3001 \ 123 | ghcr.io/blinklabs-io/cardano-node run 124 | ``` 125 | 126 | The above maps port 3001 on the host to 3001 on the container, suitable 127 | for use as a mainnet relay node. We use docker volumes for our persistent data 128 | storage and IPC (interprocess communication) so they live beyond the lifetime 129 | of the container. To mount local host paths instead of using Docker's built in 130 | volumes, use the local path on the left side of a `-v` argument. 131 | 132 | An example of a more advanced configuration, running a block producer for 133 | mainnet: 134 | 135 | ```bash 136 | docker run --detach \ 137 | --name cardano-node \ 138 | --restart unless-stopped \ 139 | -e CARDANO_BLOCK_PRODUCER=true \ 140 | -e CARDANO_SHELLEY_KES_KEY=/opt/cardano/config/keys/kes.skey \ 141 | -e CARDANO_SHELLEY_OPERATIONAL_CERTIFICATE=/opt/cardano/config/keys/node.cert \ 142 | -e CARDANO_SHELLEY_VRF_KEY=/opt/cardano/config/keys/vrf.skey \ 143 | -v /srv/cardano/node-keys:/opt/cardano/config/keys \ 144 | -v /srv/cardano/node-db:/data/db \ 145 | -v /srv/cardano/node-ipc:/ipc \ 146 | -p 3001:3001 \ 147 | -p 12798:12798 \ 148 | ghcr.io/blinklabs-io/cardano-node run 149 | ``` 150 | 151 | ##### Dynamic Block Forging 152 | 153 | To start a block producer in non-producing mode initially (for dynamic block forging): 154 | 155 | ```bash 156 | docker run --detach \ 157 | --name cardano-node \ 158 | --restart unless-stopped \ 159 | -e CARDANO_BLOCK_PRODUCER=true \ 160 | -e START_AS_NON_PRODUCING=true \ 161 | -e CARDANO_SHELLEY_KES_KEY=/opt/cardano/config/keys/kes.skey \ 162 | -e CARDANO_SHELLEY_OPERATIONAL_CERTIFICATE=/opt/cardano/config/keys/node.cert \ 163 | -e CARDANO_SHELLEY_VRF_KEY=/opt/cardano/config/keys/vrf.skey \ 164 | -v /srv/cardano/node-keys:/opt/cardano/config/keys \ 165 | -v /srv/cardano/node-db:/data/db \ 166 | -v /srv/cardano/node-ipc:/ipc \ 167 | -p 3001:3001 \ 168 | -p 12798:12798 \ 169 | ghcr.io/blinklabs-io/cardano-node run 170 | ``` 171 | 172 | With dynamic block forging enabled, you can control block production without restarting the node: 173 | 174 | - **Enable block forging**: Ensure credential files are present and send SIGHUP 175 | ```bash 176 | docker exec cardano-node pkill -SIGHUP cardano-node 177 | ``` 178 | 179 | - **Disable block forging**: Move/rename credential files and send SIGHUP 180 | ```bash 181 | docker exec cardano-node mv /opt/cardano/config/keys/kes.skey /opt/cardano/config/keys/kes.skey.disabled 182 | docker exec cardano-node pkill -SIGHUP cardano-node 183 | ``` 184 | 185 | - **Re-enable block forging**: Restore credential files and send SIGHUP 186 | ```bash 187 | docker exec cardano-node mv /opt/cardano/config/keys/kes.skey.disabled /opt/cardano/config/keys/kes.skey 188 | docker exec cardano-node pkill -SIGHUP cardano-node 189 | ``` 190 | 191 | The above uses Docker's built in supervisor to restart a container which fails 192 | for any reason. This will also cause the container to automatically restart 193 | after a host reboot, so long as Docker is configured to start on boot. We 194 | set variables to configure a block producer and pass the paths to the 3 keys 195 | we need. Our node's persistent data and client communication socket are mapped 196 | to `/srv/cardano/node-db` and `/srv/cardano/node-ipc` on the host, 197 | respectively. This allows for running applications directly on the host which 198 | may need access to these. We also map `/srv/cardano/node-keys` on the host to 199 | a path within the container to support running as a block producer. Last, we 200 | add mapping the host's port 12798 to the container 12798, which is the port for 201 | exposing the node's metrics in Prometheus format, for monitoring. 202 | 203 | This mode of operation allows configuring multiple facets of the node using 204 | environment variables, described below. 205 | 206 | Node logs can be followed: 207 | 208 | ```bash 209 | docker logs -f -n 500 cardano-node 210 | ``` 211 | 212 | Adding the `-n 500` to the logs command limits the logs to the last 500 lines 213 | before following. 214 | 215 | #### Configuration using environment variables 216 | 217 | The power in using `run` is being able to configure the node's behavior to 218 | provide the correct type of service. 219 | 220 | This behavior can be changed via the following environment variables: 221 | 222 | - `CARDANO_CONFIG_BASE` 223 | - A directory which contains configuration files (default: 224 | `/opt/cardano/config`) 225 | - This variable is used as the base for other configuration variable default 226 | - `CARDANO_BIND_ADDR` 227 | - IP address to bind for listening (default: `0.0.0.0`) 228 | - `CARDANO_BLOCK_PRODUCER` 229 | - Set to true for a block producing node (default: false) 230 | - Requires key files and node certificates to be present to start 231 | - `CARDANO_CONFIG` 232 | - Full path to the Cardano node configuration (default: 233 | `${CARDANO_CONFIG_BASE}/mainnet/config.json`) 234 | - Use your own configuration file to modify the node behavior fully 235 | - `CARDANO_DATABASE_PATH` 236 | - A directory which contains the ledger database files (default: 237 | `/data/db`) 238 | - This is the location for persistent data storage for the ledger 239 | - `CARDANO_PORT` 240 | - TCP port to bind for listening (default: `3001`) 241 | - `CARDANO_RTS_OPTS` 242 | - Controls the Cardano node's Haskell runtime (default: 243 | `-N2 -A64m -I0 -qg -qb --disable-delayed-os-memory-return`) 244 | - This allows tuning the node for specific use cases or resource contraints 245 | - `CARDANO_SOCKET_PATH` 246 | - UNIX socket path for listening (default: `/ipc/node.socket`) 247 | - This socket speaks Ouroboros NtC and is used by client software 248 | - `CARDANO_TOPOLOGY` 249 | - Full path to the Cardano node topology (default: 250 | `${CARDANO_CONFIG_BASE}/mainnet/topology.json`) 251 | 252 | #### Running a block producer 253 | 254 | To run a block producing node, you should, at minimum, configure your topology 255 | to recommended settings and have only your own relays listed. You will need to 256 | set `CARDANO_BLOCK_PRODUCER` to `true` and provide the appropriate key files 257 | and operational certificate. 258 | 259 | - `CARDANO_SHELLEY_KES_KEY` 260 | - Stake pool hot key, authenticates (default: 261 | `${CARDANO_CONFIG_BASE}/keys/kes.skey`) 262 | - `CARDANO_SHELLEY_VRF_KEY` 263 | - Stake pool signing key, verifies (default: 264 | `${CARDANO_CONFIG_BASE}/keys/vrf.skey`) 265 | - `CARDANO_SHELLEY_OPERATIONAL_CERTIFICATE` 266 | - Stake pool identity certificate (default: 267 | `${CARDANO_CONFIG_BASE}/keys/node.cert`) 268 | - `START_AS_NON_PRODUCING` 269 | - Start block producer node in non-producing mode (default: `false`) 270 | - When set to `true`, adds the `--start-as-non-producing-node` flag 271 | - Only applies when `CARDANO_BLOCK_PRODUCER=true` 272 | - Enables dynamic block forging control via SIGHUP signals 273 | 274 | #### Controlling Mithril snapshots 275 | 276 | If the container does not find a protocolMagicId file within the 277 | `CARDANO_DATABASE_PATH` location, it will initiate Mithril snapshot downloads 278 | for preprod and mainnet networks. This can be disabled by setting 279 | `RESTORE_SNAPSHOT` to `false`. 280 | 281 | - `AGGREGATOR_ENDPOINT` 282 | - Mithril Aggregator URL (default: 283 | `https://aggregator.release-${CARDANO_NETWORK}.api.mithril.network/aggregator`) 284 | - `GENESIS_VERIFICATION_KEY` 285 | - Network specific Genesis Verification Key (default: 286 | `reads file at: ${CARDANO_CONFIG_BASE}/${CARDANO_NETWORK}/genesis.vkey`) 287 | - `SNAPSHOT_DIGEST` 288 | - Digest identifier to fetch (default: `latest`) 289 | 290 | #### Running the Cardano CLI 291 | 292 | To run a Cardano CLI command, we use the `node-ipc` volume from our node. 293 | 294 | ```bash 295 | docker run --rm -ti \ 296 | -v node-ipc:/ipc \ 297 | ghcr.io/blinklabs-io/cardano-node cli 298 | ``` 299 | 300 | This can be added to a shell alias: 301 | 302 | ```bash 303 | alias cardano-cli="docker run --rm -ti \ 304 | -v node-ipc:/ipc \ 305 | ghcr.io/blinklabs-io/cardano-node cli" 306 | ``` 307 | 308 | Now, you can use cardano-cli commands as you would, normally. 309 | 310 | ```bash 311 | cardano-cli query tip --mainnet 312 | ``` 313 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: publish 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | tags: ["v*.*.*"] 7 | 8 | concurrency: ${{ github.ref }} 9 | 10 | env: 11 | DOCKER_IMAGE_NAME: blinklabs/cardano-node 12 | GHCR_IMAGE_NAME: ghcr.io/blinklabs-io/cardano-node 13 | 14 | jobs: 15 | build-amd64: 16 | # runs-on: ubuntu-latest 17 | runs-on: [self-hosted, Linux, X64, ansible] 18 | permissions: 19 | contents: read 20 | packages: write 21 | steps: 22 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 https://github.com/actions/checkout/releases/tag/v6.0.1 23 | - uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 https://github.com/docker/setup-buildx-action/releases/tag/v3.11.1 24 | - name: Login to Docker Hub 25 | uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 https://github.com/docker/login-action/releases/tag/v3.6.0 26 | with: 27 | username: blinklabs 28 | password: ${{ secrets.DOCKER_PASSWORD }} # uses token 29 | - name: Login to GHCR 30 | uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 https://github.com/docker/login-action/releases/tag/v3.6.0 31 | with: 32 | registry: ghcr.io 33 | username: ${{ github.actor }} 34 | password: ${{ secrets.GITHUB_TOKEN }} 35 | - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 https://github.com/actions/cache/releases/tag/v4.3.0 36 | with: 37 | path: /tmp/.buildx-cache 38 | key: ${{ runner.os }}-${{ runner.arch }}-buildx-${{ github.sha }} 39 | restore-keys: | 40 | ${{ runner.os }}-${{ runner.arch }}-buildx- 41 | - id: meta 42 | uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0 https://github.com/docker/metadata-action/releases/tag/v5.10.0 43 | with: 44 | images: | 45 | ${{ env.DOCKER_IMAGE_NAME }} 46 | ${{ env.GHCR_IMAGE_NAME }} 47 | flavor: | 48 | latest=false 49 | suffix=-amd64 50 | tags: | 51 | # Only version, no revision 52 | type=match,pattern=v(.*)-(.*),group=1 53 | # branch 54 | type=ref,event=branch 55 | # semver 56 | type=semver,pattern={{version}} 57 | - name: push 58 | uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 https://github.com/docker/build-push-action/releases/tag/v6.18.0 59 | with: 60 | context: . 61 | push: true 62 | tags: ${{ steps.meta.outputs.tags }} 63 | labels: ${{ steps.meta.outputs.labels }} 64 | cache-from: type=local,src=/tmp/.buildx-cache 65 | cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max 66 | # TEMP fix 67 | # https://github.com/docker/build-push-action/issues/252 68 | # https://github.com/moby/buildkit/issues/1896 69 | - name: cache 70 | run: | 71 | rm -rf /tmp/.buildx-cache 72 | mv /tmp/.buildx-cache-new /tmp/.buildx-cache 73 | # TEMP fix 74 | # Something strange is happening with the manifests when we push which 75 | # breaks the downstream multi-arch-manifest, so pull and push to work 76 | # around this by resubmitting manifests 77 | - name: pull-and-push 78 | run: | 79 | for t in `echo '${{ steps.meta.outputs.tags }}'`; do 80 | docker pull $t && docker push $t 81 | done 82 | 83 | build-arm64: 84 | runs-on: ubuntu-24.04-arm 85 | permissions: 86 | contents: read 87 | packages: write 88 | steps: 89 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 https://github.com/actions/checkout/releases/tag/v6.0.1 90 | - uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 https://github.com/docker/setup-buildx-action/releases/tag/v3.11.1 91 | - name: Login to Docker Hub 92 | uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 https://github.com/docker/login-action/releases/tag/v3.6.0 93 | with: 94 | username: blinklabs 95 | password: ${{ secrets.DOCKER_PASSWORD }} # uses token 96 | - name: Login to GHCR 97 | uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 https://github.com/docker/login-action/releases/tag/v3.6.0 98 | with: 99 | registry: ghcr.io 100 | username: ${{ github.actor }} 101 | password: ${{ secrets.GITHUB_TOKEN }} 102 | - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 https://github.com/actions/cache/releases/tag/v4.3.0 103 | with: 104 | path: /tmp/.buildx-cache 105 | key: ${{ runner.os }}-${{ runner.arch }}-buildx-${{ github.sha }} 106 | restore-keys: | 107 | ${{ runner.os }}-${{ runner.arch }}-buildx- 108 | - id: meta 109 | uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0 https://github.com/docker/metadata-action/releases/tag/v5.10.0 110 | with: 111 | images: | 112 | ${{ env.DOCKER_IMAGE_NAME }} 113 | ${{ env.GHCR_IMAGE_NAME }} 114 | flavor: | 115 | latest=false 116 | suffix=-arm64v8 117 | tags: | 118 | # Only version, no revision 119 | type=match,pattern=v(.*)-(.*),group=1 120 | # branch 121 | type=ref,event=branch 122 | # semver 123 | type=semver,pattern={{version}} 124 | - name: push 125 | uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 https://github.com/docker/build-push-action/releases/tag/v6.18.0 126 | with: 127 | context: . 128 | push: true 129 | tags: ${{ steps.meta.outputs.tags }} 130 | labels: ${{ steps.meta.outputs.labels }} 131 | cache-from: type=local,src=/tmp/.buildx-cache 132 | cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max 133 | # TEMP fix 134 | # https://github.com/docker/build-push-action/issues/252 135 | # https://github.com/moby/buildkit/issues/1896 136 | - name: cache 137 | run: | 138 | rm -rf /tmp/.buildx-cache 139 | mv /tmp/.buildx-cache-new /tmp/.buildx-cache 140 | # TEMP fix 141 | # Something strange is happening with the manifests when we push which 142 | # breaks the downstream multi-arch-manifest, so pull and push to work 143 | # around this by resubmitting manifests 144 | - name: pull-and-push 145 | run: | 146 | for t in `echo '${{ steps.meta.outputs.tags }}'`; do 147 | docker pull $t && docker push $t 148 | done 149 | 150 | multi-arch-manifest: 151 | runs-on: [self-hosted, Linux, X64, ansible] 152 | needs: [build-amd64, build-arm64] 153 | permissions: 154 | contents: read 155 | packages: write 156 | steps: 157 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 https://github.com/actions/checkout/releases/tag/v6.0.1 158 | - uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 https://github.com/docker/setup-buildx-action/releases/tag/v3.11.1 159 | - name: Login to Docker Hub 160 | uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 https://github.com/docker/login-action/releases/tag/v3.6.0 161 | with: 162 | username: blinklabs 163 | password: ${{ secrets.DOCKER_PASSWORD }} # uses token 164 | - name: Login to GHCR 165 | uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 https://github.com/docker/login-action/releases/tag/v3.6.0 166 | with: 167 | registry: ghcr.io 168 | username: ${{ github.actor }} 169 | password: ${{ secrets.GITHUB_TOKEN }} 170 | - id: meta-dockerhub 171 | name: Metadata - Docker Hub 172 | uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0 https://github.com/docker/metadata-action/releases/tag/v5.10.0 173 | with: 174 | images: ${{ env.DOCKER_IMAGE_NAME }} 175 | flavor: | 176 | latest=false 177 | tags: | 178 | # Only version, no revision 179 | type=match,pattern=v(.*)-(.*),group=1 180 | # branch 181 | type=ref,event=branch 182 | # semver 183 | type=semver,pattern={{version}} 184 | - id: meta-dockerhub-tag 185 | name: Metadata - Docker Hub (Tags) 186 | uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0 https://github.com/docker/metadata-action/releases/tag/v5.10.0 187 | with: 188 | images: | 189 | ${{ env.DOCKER_IMAGE_NAME }} 190 | flavor: | 191 | latest=false 192 | tags: | 193 | # Only version, no revision 194 | type=match,pattern=v(.*)-(.*),group=1 195 | - id: meta-ghcr 196 | name: Metadata - GHCR 197 | uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0 https://github.com/docker/metadata-action/releases/tag/v5.10.0 198 | with: 199 | images: ${{ env.GHCR_IMAGE_NAME }} 200 | flavor: | 201 | latest=false 202 | tags: | 203 | # Only version, no revision 204 | type=match,pattern=v(.*)-(.*),group=1 205 | # branch 206 | type=ref,event=branch 207 | # semver 208 | type=semver,pattern={{version}} 209 | - id: meta-ghcr-tag 210 | name: Metadata - GHCR (Tags) 211 | uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0 https://github.com/docker/metadata-action/releases/tag/v5.10.0 212 | with: 213 | images: | 214 | ${{ env.GHCR_IMAGE_NAME }} 215 | flavor: | 216 | latest=false 217 | tags: | 218 | # Only version, no revision 219 | type=match,pattern=v(.*)-(.*),group=1 220 | 221 | # First, create manifests and push to GHCR 222 | 223 | # Manifest for either branch or semver 224 | - name: manifest-ghcr 225 | run: | 226 | for t in `echo '${{ steps.meta-ghcr.outputs.tags }}'`; do 227 | docker manifest create ${t} --amend ${t}-amd64 --amend ${t}-arm64v8 228 | done 229 | # Optional manifest for tag versions (includes revisions) 230 | - name: manifest-ghcr-tags 231 | run: | 232 | for t in `echo '${{ steps.meta-ghcr-tag.outputs.tags }}'`; do 233 | docker manifest create ${t} --amend ${t}-amd64 --amend ${t}-arm64v8 234 | docker manifest create ${{ env.GHCR_IMAGE_NAME }}:latest --amend ${t}-amd64 --amend ${t}-arm64v8 235 | done 236 | if: startsWith(github.ref, 'refs/tags/') && ! contains(github.ref, '-pre-') 237 | # Push various manifests 238 | - name: push-ghcr 239 | run: | 240 | for t in `echo '${{ steps.meta-ghcr.outputs.tags }}'`; do 241 | docker manifest push ${t} 242 | done 243 | - name: push-ghcr-tags 244 | run: | 245 | docker manifest push ${{ env.GHCR_IMAGE_NAME }}:latest 246 | for t in `echo '${{ steps.meta-ghcr-tag.outputs.tags }}'`; do 247 | docker manifest push ${t} 248 | done 249 | if: startsWith(github.ref, 'refs/tags/') && ! contains(github.ref, '-pre-') 250 | 251 | # Now, create manifests for Docker Hub 252 | 253 | - name: manifest-dockerhub 254 | run: | 255 | for t in `echo '${{ steps.meta-dockerhub.outputs.tags }}'`; do 256 | docker manifest create ${t} --amend ${t}-amd64 --amend ${t}-arm64v8 257 | done 258 | - name: manifest-dockerhub-tags 259 | run: | 260 | for t in `echo '${{ steps.meta-dockerhub-tag.outputs.tags }}'`; do 261 | docker manifest create ${t} --amend ${t}-amd64 --amend ${t}-arm64v8 262 | docker manifest create ${{ env.DOCKER_IMAGE_NAME }}:latest --amend ${t}-amd64 --amend ${t}-arm64v8 263 | done 264 | if: startsWith(github.ref, 'refs/tags/') && ! contains(github.ref, '-pre-') 265 | - name: push-dockerhub 266 | run: | 267 | for t in `echo '${{ steps.meta-dockerhub.outputs.tags }}'`; do 268 | docker manifest push ${t} 269 | done 270 | - name: push-dockerhub-tags 271 | run: | 272 | docker manifest push ${{ env.DOCKER_IMAGE_NAME }}:latest 273 | for t in `echo '${{ steps.meta-dockerhub-tag.outputs.tags }}'`; do 274 | docker manifest push ${t} 275 | done 276 | if: startsWith(github.ref, 'refs/tags/') && ! contains(github.ref, '-pre-') 277 | 278 | # Update Docker Hub from README 279 | 280 | - name: Docker Hub Description 281 | uses: peter-evans/dockerhub-description@1b9a80c056b620d92cedb9d9b5a223409c68ddfa # v5.0.0 https://github.com/peter-evans/dockerhub-description/releases/tag/v5.0.0 282 | with: 283 | username: blinklabs 284 | password: ${{ secrets.DOCKER_PASSWORD }} 285 | repository: ${{ env.DOCKER_IMAGE_NAME }} 286 | readme-filepath: ./README.md 287 | short-description: "Cardano Node built from source on Debian" 288 | 289 | github-release: 290 | runs-on: [self-hosted, Linux, X64, ansible] 291 | permissions: 292 | contents: write 293 | needs: [multi-arch-manifest] 294 | steps: 295 | - run: 'echo "RELEASE_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV' 296 | - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 https://github.com/actions/github-script/releases/tag/v8.0.0 297 | if: startsWith(github.ref, 'refs/tags/') 298 | with: 299 | github-token: ${{ secrets.GITHUB_TOKEN }} 300 | script: | 301 | try { 302 | await github.rest.repos.createRelease({ 303 | draft: false, 304 | generate_release_notes: true, 305 | name: process.env.RELEASE_TAG, 306 | owner: context.repo.owner, 307 | prerelease: ${{ (startsWith(github.ref, 'refs/tags/') && contains(github.ref, '-pre-')) && true || false }}, 308 | repo: context.repo.repo, 309 | tag_name: process.env.RELEASE_TAG, 310 | }); 311 | } catch (error) { 312 | core.setFailed(error.message); 313 | } 314 | --------------------------------------------------------------------------------