diff --git a/docker/.dockerignore b/docker/.dockerignore new file mode 100644 index 000000000..591bfe816 --- /dev/null +++ b/docker/.dockerignore @@ -0,0 +1,23 @@ +# Ignore common build artifacts and large nix store paths +result +result.tar.gz +result-*.tar.gz +dist +dist-newstyle +dist/* +*.o +*.hi +.stack-work +.ghc.environment.* +.cabal-sandbox +.cabal-sandbox/* +node_modules +.git +*.lock +**/.DS_Store + +# Ignore Nix store references and temporary files +/nix +*.drv +*.nar + diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 000000000..cbad41a25 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,46 @@ +# Lightweight Dockerfile for a static cardano-db-sync binary +# +# Usage (example): +# 1. Build a static executable with Nix: +# nix build .#cardano-db-sync-linux +# 2. Build the image from the project root: +# docker build -f docker/Dockerfile -t cardano-db-sync:lightweight . +# +# Note: Since result/ is a Nix symlink, Docker may have issues following it. +# If this fails, you can dereference the symlink first: +# mkdir -p docker/build && cp -L result/*.tar.gz docker/build/ +# docker build -f docker/Dockerfile -t cardano-db-sync:lightweight . +# +# Important: this Dockerfile assumes the binary is a fully static executable +# (statically linked). If it's not static, use a small base image (see docs). + +# Stage 1: Extract the binary from the tarball +FROM busybox:latest AS extractor +ARG TARBALL +# ADD automatically extracts tarballs +ADD ${TARBALL} /tmp/extract/ +RUN find /tmp/extract -name cardano-db-sync -type f -exec mv {} /cardano-db-sync \; && \ + chmod +x /cardano-db-sync + +# Stage 2: Minimal runtime image +FROM ubuntu:24.04 +ARG SCHEMA_HASH + +# copy binary from extractor +COPY --from=extractor /cardano-db-sync /usr/local/bin/cardano-db-sync + +# copy schema directory (must be present in build context) +COPY schema /schema +RUN chmod -R 755 /schema + +# copy entrypoint template then substitute the baked SCHEMA_HASH into it +COPY docker-entrypoint.sh.template /usr/local/bin/docker-entrypoint.sh.template + +# Use sed to bake the SCHEMA_HASH into the entrypoint script, then cleanup template +RUN sed "s|@SCHEMA_HASH@|${SCHEMA_HASH}|g" /usr/local/bin/docker-entrypoint.sh.template \ + > /usr/local/bin/docker-entrypoint.sh && \ + chmod +x /usr/local/bin/docker-entrypoint.sh && \ + rm -f /usr/local/bin/docker-entrypoint.sh.template + +ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] +CMD ["--help"] diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 000000000..d06b34099 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,9 @@ +Lightweight Docker image for cardano-db-sync +========================================== + +This folder contains a minimal Docker context and `Dockerfile` intended for use with +a statically-built `cardano-db-sync` executable produced by Nix. + +## testing + +docker run --rm cardano-db-sync:lightweight version diff --git a/docker/build.sh b/docker/build.sh new file mode 100755 index 000000000..69c7dd6ab --- /dev/null +++ b/docker/build.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Dereference the Nix result symlink and get the tarball basename +TARBALL=$(basename "$(readlink -f result/*.tar.gz)") + +# copy tarball into docker build context +cp -L result/*.tar.gz docker/ + +# copy runtime schema into build context so it gets baked into the image +cp -a schema docker/ + +# compute a deterministic schema checksum (sorted file list -> combined sha) +SCHEMA_HASH=$(cd schema && find . -type f -print0 | LC_ALL=C sort -z | xargs -0 sha256sum | sha256sum | awk '{print $1}') + +# Build with the exact tarball name and schema hash as build args +docker build --build-arg TARBALL="$TARBALL" --build-arg SCHEMA_HASH="$SCHEMA_HASH" -t cardano-db-sync:lightweight docker/ + +# Cleanup +rm -f docker/*.tar.gz +rm -rf docker/schema diff --git a/docker/docker-entrypoint.sh.template b/docker/docker-entrypoint.sh.template new file mode 100644 index 000000000..c9ca2bda9 --- /dev/null +++ b/docker/docker-entrypoint.sh.template @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -eu + +# compute runtime checksum deterministically (sorted file list) +runtime_hash=$(cd /schema && find . -type f -print0 | LC_ALL=C sort -z | xargs -0 sha256sum | sha256sum | awk '{print $1}') + +# baked checksum will be substituted at build time +if [ "$runtime_hash" != "@SCHEMA_HASH@" ]; then + echo "schema checksum mismatch: runtime=$runtime_hash baked=@SCHEMA_HASH@" >&2 + exit 1 +fi + +exec /usr/local/bin/cardano-db-sync --schema-dir /schema "$@" \ No newline at end of file diff --git a/docker/test-image.sh b/docker/test-image.sh new file mode 100755 index 000000000..cdf0eaa70 --- /dev/null +++ b/docker/test-image.sh @@ -0,0 +1,131 @@ +#!/usr/bin/env bash +# Test the cardano-db-sync Docker image: +# - basic invocation (defaults to --help) +# - running with the original schema directory (should succeed) +# - running with a modified schema (should fail and report checksum mismatch) +# - check that the binary exists by bypassing the ENTRYPOINT +# +# Usage: ./test-image.sh [IMAGE] [SCHEMA_DIR] +# Defaults: +# IMAGE=cardano-db-sync:lightweight +# SCHEMA_DIR=./schema + +set -uo pipefail + +IMAGE="${1:-cardano-db-sync:lightweight}" +SCHEMA_DIR="${2:-./schema}" + +TMPDIR="" +FAILED=0 + +cleanup() { + rc=$? + if [[ -n "$TMPDIR" && -d "$TMPDIR" ]]; then + rm -rf "$TMPDIR" + fi + exit $rc +} +trap cleanup EXIT + +require_cmd() { + command -v "$1" >/dev/null 2>&1 || { echo "required command '$1' not found" >&2; exit 2; } +} + +require_cmd docker +require_cmd mktemp +require_cmd cp +require_cmd sed +require_cmd realpath + +echo "Image: $IMAGE" +echo "Schema dir: $SCHEMA_DIR" + +if ! docker image inspect "$IMAGE" >/dev/null 2>&1; then + echo "ERROR: Docker image '$IMAGE' not found locally." >&2 + echo "Either build it or pull it before running this test script." >&2 + exit 3 +fi + +run_container_capture() { + # args: container args... + docker run --rm "$@" 2>&1 + return ${PIPESTATUS[0]:-0} +} + +echo +echo "1) Basic invocation (no extra mounts) - should exit 0 (show help)" +out=$(run_container_capture "$IMAGE") +rc=$? +if [[ $rc -eq 0 ]]; then + echo "PASS: container exited 0" +else + echo "FAIL: container exited with $rc" + echo "Output:" + echo "-----" + echo "$out" + echo "-----" + FAILED=$((FAILED+1)) +fi + +if [[ ! -d "$SCHEMA_DIR" ]]; then + echo + echo "WARNING: schema directory '$SCHEMA_DIR' not found; skipping schema-related tests." +else + echo + echo "2) Run with host schema mounted to /schema (expected: success)" + out=$(run_container_capture -v "$(realpath "$SCHEMA_DIR")":/schema:ro "$IMAGE") + rc=$? + if [[ $rc -eq 0 ]]; then + echo "PASS: container exited 0 with host schema" + else + echo "FAIL: container exited $rc with host schema" + echo "Output:" + echo "-----" + echo "$out" + echo "-----" + FAILED=$((FAILED+1)) + fi + + echo + echo "3) Run with a modified schema (expected: schema checksum mismatch -> non-zero and error text)" + TMPDIR=$(mktemp -d) + cp -a "$SCHEMA_DIR"/. "$TMPDIR"/ + # Modify the schema to change checksum (add a small file) + echo "injected-change-$(date +%s)" > "$TMPDIR"/.injected-change + + out=$(run_container_capture -v "$TMPDIR":/schema:ro "$IMAGE") + rc=$? + if [[ $rc -ne 0 && "$out" == *"schema checksum mismatch"* ]]; then + echo "PASS: schema mismatch detected as expected (exit $rc)" + else + echo "FAIL: expected schema mismatch (non-zero exit and 'schema checksum mismatch' message)." + echo "Exit code: $rc" + echo "Output:" + echo "-----" + echo "$out" + echo "-----" + FAILED=$((FAILED+1)) + fi +fi + +echo +echo "4) Binary presence check inside image (override ENTRYPOINT so we can inspect fs)" +# IMPORTANT: override the image ENTRYPOINT so our test runs directly in the container +if docker run --rm --entrypoint /bin/sh "$IMAGE" -c 'test -x /usr/local/bin/cardano-db-sync' >/dev/null 2>&1; then + echo "PASS: /usr/local/bin/cardano-db-sync exists and is executable (entrypoint bypassed)" + echo " File info:" + docker run --rm --entrypoint /bin/sh "$IMAGE" -c 'ls -la /usr/local/bin/cardano-db-sync || true; file /usr/local/bin/cardano-db-sync || true' +else + echo "FAIL: /usr/local/bin/cardano-db-sync missing or not executable (even with entrypoint bypassed)" + FAILED=$((FAILED+1)) +fi + +echo +if [[ $FAILED -eq 0 ]]; then + echo "ALL TESTS PASSED" +else + echo "SOME TESTS FAILED: $FAILED failure(s)" + exit 4 +fi + +# cleanup happens via trap