diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml new file mode 100644 index 000000000..e2cf9334a --- /dev/null +++ b/.github/workflows/docker.yaml @@ -0,0 +1,97 @@ +name: Docker + +on: + push: + branches: + - main + - v1 + - release/* + paths: + - "docker/**" + - ".github/workflows/docker.yaml" + pull_request: + branches: + - main + - v1 + paths: + - "docker/**" + - ".github/workflows/docker.yaml" + types: [opened, reopened, synchronize, ready_for_review] + workflow_dispatch: + inputs: + process: + description: 'Process to build (pgvector | node-sqitch | postgis)' + type: choice + required: true + options: + - pgvector + - node-sqitch + - postgis + - pgvector-postgis + default: pgvector + version: + description: 'Specific version to build (must exist in version.yaml)' + type: string + required: true + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-docker + cancel-in-progress: true + +jobs: + build-push: + if: github.event_name != 'pull_request' || !github.event.pull_request.draft + runs-on: ubuntu-latest + + permissions: + contents: read + packages: write + + defaults: + run: + working-directory: docker + + strategy: + matrix: + process: [pgvector, node-sqitch, postgis, pgvector-postgis] + max-parallel: 3 + + env: + REPO: ghcr.io/${{ github.repository_owner }} + PLATFORMS: linux/amd64,linux/arm64 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GHCR + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build (no push) + if: github.event_name == 'pull_request' + run: | + make \ + PROCESS=${{ matrix.process }} \ + REPO_NAME=$REPO \ + PLATFORMS="$PLATFORMS" \ + build-process + + - name: Build and push (all versions) + if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' + run: | + make \ + PROCESS=${{ matrix.process }} \ + REPO_NAME=$REPO \ + PLATFORMS="$PLATFORMS" \ + build-push-process diff --git a/docker/Makefile b/docker/Makefile index 54158c7e7..5964b5090 100644 --- a/docker/Makefile +++ b/docker/Makefile @@ -1,20 +1,93 @@ -.PHONY: all push-all node-sqitch pgvector clean +.PHONY: all push-all build-all build-push-all build-process build-push-process \ + build-process-version build-push-process-version pgvector node-sqitch postgis clean -all: - $(MAKE) -C node-sqitch build - $(MAKE) -C pgvector build +REPO_NAME?=pyramation +PLATFORMS?=linux/arm64 -push-all: - $(MAKE) -C node-sqitch push - $(MAKE) -C pgvector push +# default process if none specified (can be overridden: `make PROCESS=postgis build-process`) +PROCESS?=pgvector -node-sqitch: - $(MAKE) -C node-sqitch build +# Convenience: list of known processes +PROCESSES:=pgvector node-sqitch postgis pgvector-postgis + +CONTAINER_NAME?=$(PROCESS) + +## build-process builds docker image(s) for $(PROCESS) using base+versions from version.yaml +## It will build one image per version listed in $(PROCESS)/version.yaml and tag as /: +build-process: + @echo "==> Building process: $(PROCESS)" + @BASE=$$(sed -n 's/^[[:space:]]*base:[[:space:]]*//p' $(PROCESS)/version.yaml | head -n1); \ + VERSIONS=$$(sed -n '/^[[:space:]]*versions:/,$$p' $(PROCESS)/version.yaml | sed -n 's/^[[:space:]]*-[[:space:]]*//p'); \ + if [ -z "$$BASE" ] || [ -z "$$VERSIONS" ]; then \ + echo "Error: Could not parse base or versions from $(PROCESS)/version.yaml" 1>&2; \ + exit 1; \ + fi; \ + for v in $$VERSIONS; do \ + $(MAKE) --no-print-directory BASE=$$BASE VERSION=$$v build-process-version || exit $$?; \ + done + +build-all: + @for p in $(PROCESSES); do \ + $(MAKE) --no-print-directory PROCESS=$$p build-process || exit $$?; \ + done + +build-push-process: + @echo "==> Building+Pushing process: $(PROCESS)" + @BASE=$$(sed -n 's/^[[:space:]]*base:[[:space:]]*//p' $(PROCESS)/version.yaml | head -n1); \ + VERSIONS=$$(sed -n '/^[[:space:]]*versions:/,$$p' $(PROCESS)/version.yaml | sed -n 's/^[[:space:]]*-[[:space:]]*//p'); \ + if [ -z "$$BASE" ] || [ -z "$$VERSIONS" ]; then \ + echo "Error: Could not parse base or versions from $(PROCESS)/version.yaml" 1>&2; \ + exit 1; \ + fi; \ + for v in $$VERSIONS; do \ + $(MAKE) --no-print-directory BASE=$$BASE VERSION=$$v build-push-process-version || exit $$?; \ + done + +build-push-all: + @for p in $(PROCESSES); do \ + $(MAKE) --no-print-directory PROCESS=$$p build-push-process || exit $$?; \ + done +# Build only a specific VERSION for $(PROCESS). Intended for internal use by build-process. +# Usage (internal): $(MAKE) BASE= VERSION= build-process-version +build-process-version: + @test -n "$(BASE)" || { echo "Error: BASE is required"; exit 1; } + @test -n "$(VERSION)" || { echo "Error: VERSION is required"; exit 1; } + @echo " -> $(BASE):$(VERSION) => $(REPO_NAME)/$(PROCESS):$(VERSION) (build)" + @docker buildx build \ + --platform $(PLATFORMS) \ + --build-arg BASE=$(BASE) \ + --build-arg BASE_VERSION=$(VERSION) \ + -t $(REPO_NAME)/$(PROCESS):$(VERSION) \ + $(PROCESS) + +# Build+push only a specific VERSION for $(PROCESS). Intended for internal use by build-push-process. +# Usage (internal): $(MAKE) BASE= VERSION= build-push-process-version +build-push-process-version: + @test -n "$(BASE)" || { echo "Error: BASE is required"; exit 1; } + @test -n "$(VERSION)" || { echo "Error: VERSION is required"; exit 1; } + @echo " -> $(BASE):$(VERSION) => $(REPO_NAME)/$(PROCESS):$(VERSION) (push)" + @docker buildx build \ + --platform $(PLATFORMS) \ + --build-arg BASE=$(BASE) \ + --build-arg BASE_VERSION=$(VERSION) \ + -t $(REPO_NAME)/$(PROCESS):$(VERSION) \ + --push \ + $(PROCESS) + +# Aliases +all: build-all +push-all: build-push-all + +# Convenience per-process targets pgvector: - $(MAKE) -C pgvector build + $(MAKE) PROCESS=pgvector build-process + +node-sqitch: + $(MAKE) PROCESS=node-sqitch build-process + +postgis: + $(MAKE) PROCESS=postgis build-process -# Git cleanup (repo-level only) -clean: - @git reset --hard - @git ls-files --other --exclude-standard | xargs rm -f +pgvector-postgis: + $(MAKE) PROCESS=pgvector-postgis build-process diff --git a/docker/node-sqitch/Dockerfile b/docker/node-sqitch/Dockerfile index e03365426..6752da686 100644 --- a/docker/node-sqitch/Dockerfile +++ b/docker/node-sqitch/Dockerfile @@ -1,5 +1,8 @@ -FROM node:20.12.0-alpine3.19 AS sqitch-build +ARG BASE=node +ARG BASE_VERSION=20.12.0-alpine3.19 +FROM ${BASE}:${BASE_VERSION} AS sqitch-build +LABEL org.opencontainers.image.source="https://github.com/launchql/launchql" # Install system dependencies. WORKDIR /work @@ -46,7 +49,11 @@ RUN apk del .build-deps ################################################################################ # Copy to the final image without all the build stuff. -FROM node:20.12.0-alpine3.19 AS sqitch +ARG BASE=node +ARG BASE_VERSION=20.12.0-alpine3.19 +FROM ${BASE}:${BASE_VERSION} AS sqitch + +LABEL org.opencontainers.image.source="https://github.com/launchql/launchql" # Install runtime system dependencies and remove unnecesary files. RUN mkdir -p /usr/share/man/man1 /usr/share/man/man7 \ @@ -82,4 +89,4 @@ ENV LESS=-R LC_ALL=C.UTF-8 LANG=C.UTF-8 SQITCH_EDITOR=vi SQITCH_PAGER=less # for gyp and such RUN apk update && apk add --no-cache bash git python3-dev make g++ -ENTRYPOINT ["/bin/sh"] \ No newline at end of file +ENTRYPOINT ["/bin/sh"] diff --git a/docker/node-sqitch/Makefile b/docker/node-sqitch/Makefile deleted file mode 100644 index 229fe3039..000000000 --- a/docker/node-sqitch/Makefile +++ /dev/null @@ -1,39 +0,0 @@ -IMAGE := pyramation/node-sqitch -TAG := 20.12.0 -PLATFORMS := linux/amd64,linux/arm64 -CONTAINER_NAME := node-sqitch - -.PHONY: build push tag-latest ssh cleanup clean-images - -build: - docker buildx build \ - --platform $(PLATFORMS) \ - -t $(IMAGE):$(TAG) \ - --output=type=docker \ - . - -push: tag-latest - docker buildx build \ - --platform $(PLATFORMS) \ - -t $(IMAGE):$(TAG) \ - -t $(IMAGE):latest \ - --push \ - . - -tag-latest: - docker tag $(IMAGE):$(TAG) $(IMAGE):latest - -ssh: - docker run --platform=linux/arm64 -it $(IMAGE):$(TAG) sh - -cleanup: - @if [ ! -z "$$(docker ps -aq -f name=$(CONTAINER_NAME))" ]; then \ - echo "Stopping and removing existing container..."; \ - docker stop $(CONTAINER_NAME) && docker rm $(CONTAINER_NAME); \ - fi - -clean-images: - @if docker images | grep $(IMAGE); then \ - echo "Removing Docker images for $(IMAGE)..."; \ - docker rmi $(IMAGE):$(TAG) $(IMAGE):latest || true; \ - fi diff --git a/docker/node-sqitch/version.yaml b/docker/node-sqitch/version.yaml new file mode 100644 index 000000000..5cceb380a --- /dev/null +++ b/docker/node-sqitch/version.yaml @@ -0,0 +1,3 @@ +base: node +versions: +- 20.12.0-alpine3.19 diff --git a/docker/pgvector-postgis/Dockerfile b/docker/pgvector-postgis/Dockerfile new file mode 100644 index 000000000..94ee4a0ec --- /dev/null +++ b/docker/pgvector-postgis/Dockerfile @@ -0,0 +1,30 @@ +ARG BASE=postgres +ARG BASE_VERSION=14 +FROM ${BASE}:${BASE_VERSION} + +LABEL org.opencontainers.image.source="https://github.com/launchql/launchql" +ARG BASE +ARG BASE_VERSION +ENV BASE_VERSION=${BASE_VERSION} + +# Debian-based: install both pgvector and postgis from PGDG per-PG-major +RUN set -eux; \ + export DEBIAN_FRONTEND=noninteractive; \ + apt-get update; \ + apt-get install -y --no-install-recommends ca-certificates curl gnupg dirmngr; \ + update-ca-certificates || true; \ + CODENAME=$(. /etc/os-release && echo "$VERSION_CODENAME"); \ + install -d -m 0755 /usr/share/keyrings; \ + curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor -o /usr/share/keyrings/postgresql.gpg; \ + echo "deb [signed-by=/usr/share/keyrings/postgresql.gpg] https://apt.postgresql.org/pub/repos/apt ${CODENAME}-pgdg main" > /etc/apt/sources.list.d/pgdg.list; \ + apt-get update; \ + PG_MAJOR=$(pg_config --version | sed 's/^PostgreSQL \([0-9]\+\).*/\1/'); \ + apt-get install -y --no-install-recommends \ + postgresql-${PG_MAJOR}-postgis-3 \ + postgresql-${PG_MAJOR}-postgis-3-scripts \ + postgis \ + postgresql-${PG_MAJOR}-pgvector \ + make \ + bash; \ + rm -rf /var/lib/apt/lists/* + diff --git a/docker/pgvector-postgis/version.yaml b/docker/pgvector-postgis/version.yaml new file mode 100644 index 000000000..56534546b --- /dev/null +++ b/docker/pgvector-postgis/version.yaml @@ -0,0 +1,8 @@ +base: postgres +versions: + - 14.19 + - 15.14 + - 16.10 + - 17.6 + - 18.0 + diff --git a/docker/pgvector/Dockerfile b/docker/pgvector/Dockerfile index 9c981d847..dca8b321b 100644 --- a/docker/pgvector/Dockerfile +++ b/docker/pgvector/Dockerfile @@ -1,12 +1,26 @@ -FROM pyramation/postgis:13.3-alpine +ARG BASE=postgres +ARG BASE_VERSION=14 +FROM ${BASE}:${BASE_VERSION} -# Install PGVector extension -RUN apk add --no-cache --virtual .build-deps \ - git \ - build-base \ - postgresql-dev \ - && git clone --branch v0.5.1 https://github.com/pgvector/pgvector.git \ - && cd pgvector \ - && make && make install \ - && cd .. && rm -rf pgvector \ - && apk del .build-deps \ No newline at end of file +LABEL org.opencontainers.image.source="https://github.com/launchql/launchql" +ARG BASE +ARG BASE_VERSION +ENV BASE_VERSION=${BASE_VERSION} + +# Debian-based install: use PGDG per-PG-major package +RUN set -eux; \ + export DEBIAN_FRONTEND=noninteractive; \ + apt-get update; \ + apt-get install -y --no-install-recommends ca-certificates curl gnupg dirmngr; \ + update-ca-certificates || true; \ + CODENAME=$(. /etc/os-release && echo "$VERSION_CODENAME"); \ + install -d -m 0755 /usr/share/keyrings; \ + curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor -o /usr/share/keyrings/postgresql.gpg; \ + echo "deb [signed-by=/usr/share/keyrings/postgresql.gpg] https://apt.postgresql.org/pub/repos/apt ${CODENAME}-pgdg main" > /etc/apt/sources.list.d/pgdg.list; \ + apt-get update; \ + PG_MAJOR=$(pg_config --version | sed 's/^PostgreSQL \([0-9]\+\).*/\1/'); \ + apt-get install -y --no-install-recommends \ + postgresql-${PG_MAJOR}-pgvector \ + make \ + bash; \ + rm -rf /var/lib/apt/lists/* diff --git a/docker/pgvector/Dockerfile.alpine b/docker/pgvector/Dockerfile.alpine new file mode 100644 index 000000000..1d99282bb --- /dev/null +++ b/docker/pgvector/Dockerfile.alpine @@ -0,0 +1,29 @@ +ARG BASE=postgres +ARG BASE_VERSION=13.3-alpine +FROM ${BASE}:${BASE_VERSION} + +LABEL org.opencontainers.image.source="https://github.com/launchql/launchql" +ARG BASE +ARG BASE_VERSION +ARG PGVECTOR_REF="v0.8.1" +ENV BASE_VERSION=${BASE_VERSION} +ENV PGVECTOR_REF=${PGVECTOR_REF} + +# Alpine build (kept as a backup variant) +RUN set -eux; \ + : "${BASE_VERSION:?BASE_VERSION not set}"; \ + apk add --no-cache --virtual .build-deps git build-base bash make curl ca-certificates; \ + LLVM_CFG=$(pg_config --configure | tr ' ' '\n' | sed -n 's/^LLVM_CONFIG=\(.*\)$/\1/p' | tr -d '\"\'\'' ); \ + [ -n "${LLVM_CFG}" ] || { echo "Server not built with LLVM (LLVM_CONFIG missing)" >&2; exit 1; }; \ + LLVM_VER=$(echo "${LLVM_CFG}" | sed -E 's#.*/llvm-?([0-9]+)/.*#\1#'); \ + [ -n "${LLVM_VER}" ] || { echo "Could not determine LLVM version from: ${LLVM_CFG}" >&2; exit 1; }; \ + apk add --no-cache --virtual .clang \ + "clang${LLVM_VER}" \ + "llvm${LLVM_VER}" \ + "llvm${LLVM_VER}-libs" \ + "llvm${LLVM_VER}-linker-tools"; \ + git clone --quiet --depth 1 --branch "${PGVECTOR_REF}" https://github.com/pgvector/pgvector.git; \ + cd pgvector && make && make install; \ + cd .. && rm -rf pgvector; \ + apk del .clang .build-deps + diff --git a/docker/pgvector/Makefile b/docker/pgvector/Makefile deleted file mode 100644 index 52eb95e06..000000000 --- a/docker/pgvector/Makefile +++ /dev/null @@ -1,35 +0,0 @@ -IMAGE := pyramation/pgvector -TAG := 13.3-alpine -PLATFORMS := linux/amd64,linux/arm64 -CONTAINER_NAME := pgvector - -.PHONY: build push ssh cleanup clean-images - -build: - docker buildx build \ - --platform $(PLATFORMS) \ - -t $(IMAGE):$(TAG) \ - --output=type=docker \ - . - -push: - docker buildx build \ - --platform $(PLATFORMS) \ - -t $(IMAGE):$(TAG) \ - --push \ - . - -ssh: - docker run --platform=linux/arm64 -it $(IMAGE):$(TAG) sh - -cleanup: - @if [ ! -z "$$(docker ps -aq -f name=$(CONTAINER_NAME))" ]; then \ - echo "Stopping and removing existing container..."; \ - docker stop $(CONTAINER_NAME) && docker rm $(CONTAINER_NAME); \ - fi - -clean-images: - @if docker images | grep $(IMAGE); then \ - echo "Removing Docker images for $(IMAGE)..."; \ - docker rmi $(IMAGE):$(TAG) || true; \ - fi diff --git a/docker/pgvector/version.yaml b/docker/pgvector/version.yaml new file mode 100644 index 000000000..b126d0b08 --- /dev/null +++ b/docker/pgvector/version.yaml @@ -0,0 +1,7 @@ +base: postgres +versions: + - 14.19 + - 15.14 + - 16.10 + - 17.6 + - 18.0 diff --git a/docker/postgis/Dockerfile b/docker/postgis/Dockerfile index ac872305e..5ef75570f 100644 --- a/docker/postgis/Dockerfile +++ b/docker/postgis/Dockerfile @@ -1,3 +1,60 @@ -FROM postgres:13.3-alpine +ARG BASE=postgres +ARG BASE_VERSION=13.3 +FROM ${BASE}:${BASE_VERSION} -RUN apk add make \ No newline at end of file +LABEL org.opencontainers.image.source="https://github.com/launchql/launchql" +ARG BASE +ARG BASE_VERSION +ENV BASE_VERSION=${BASE_VERSION} + +# Install PostGIS extension +# - Detect PostgreSQL major version and install matching PostGIS packages +# - Handle archived Debian releases (Buster and earlier) +# - Ensure CA certificates exist before hitting apt-archive.postgresql.org over HTTPS +RUN set -eux; \ + export DEBIAN_FRONTEND=noninteractive; \ + PGDG_FILE="/etc/apt/sources.list.d/pgdg.list"; \ + # Temporarily disable PGDG so we can first try Debian's own repos + [ -f "$PGDG_FILE" ] && mv "$PGDG_FILE" "$PGDG_FILE.disabled" || true; \ + \ + # If the base Debian release is EOL, switch to archive.debian.org for Debian repos + if ! apt-get update 2>/dev/null; then \ + sed -i 's|http://deb.debian.org/debian|http://archive.debian.org/debian|g' /etc/apt/sources.list; \ + sed -i 's|http://security.debian.org/debian-security|http://archive.debian.org/debian-security|g' /etc/apt/sources.list; \ + sed -i '/-updates/d' /etc/apt/sources.list || true; \ + fi; \ + \ + # Update and install CA certificates so HTTPS apt repos work + apt-get update; \ + apt-get install -y --no-install-recommends ca-certificates gnupg dirmngr; \ + update-ca-certificates || true; \ + \ + # Determine server major version + PG_MAJOR=$(pg_config --version | sed 's/^PostgreSQL \([0-9]\+\).*/\1/'); \ + \ + # If Debian repos don't have PostGIS for this PG major, enable PGDG (archive only for EOL codenames) + if ! apt-cache show "postgresql-${PG_MAJOR}-postgis-3" >/dev/null 2>&1; then \ + CODENAME=$(. /etc/os-release && echo "$VERSION_CODENAME"); \ + case "$CODENAME" in \ + stretch|buster) PGDG_URL="https://apt-archive.postgresql.org/pub/repos/apt" ;; \ + *) PGDG_URL="https://apt.postgresql.org/pub/repos/apt" ;; \ + esac; \ + if [ -f "$PGDG_FILE.disabled" ]; then \ + sed -E 's#https?://(apt|apt-archive)\.postgresql\.org/pub/repos/apt#'"$PGDG_URL"'#g' "$PGDG_FILE.disabled" \ + | sed -E "s# (stretch|buster|bullseye|bookworm|trixie)-pgdg # ${CODENAME}-pgdg #g" \ + > "$PGDG_FILE"; \ + rm -f "$PGDG_FILE.disabled"; \ + else \ + echo "deb $PGDG_URL ${CODENAME}-pgdg main" > "$PGDG_FILE"; \ + fi; \ + apt-get update; \ + fi; \ + \ + # Install PostGIS matching PG major + apt-get install -y --no-install-recommends \ + postgresql-${PG_MAJOR}-postgis-3 \ + postgresql-${PG_MAJOR}-postgis-3-scripts \ + postgis \ + make \ + bash; \ + rm -rf /var/lib/apt/lists/* diff --git a/docker/postgis/Makefile b/docker/postgis/Makefile deleted file mode 100644 index 88162d3ee..000000000 --- a/docker/postgis/Makefile +++ /dev/null @@ -1,35 +0,0 @@ -IMAGE := pyramation/postgis -TAG := 13.3-alpine -PLATFORMS := linux/amd64,linux/arm64 -CONTAINER_NAME := postgis - -.PHONY: build push ssh cleanup clean-images - -build: - docker buildx build \ - --platform $(PLATFORMS) \ - -t $(IMAGE):$(TAG) \ - --output=type=docker \ - . - -push: - docker buildx build \ - --platform $(PLATFORMS) \ - -t $(IMAGE):$(TAG) \ - --push \ - . - -ssh: - docker run --platform=linux/arm64 -it $(IMAGE):$(TAG) sh - -cleanup: - @if [ ! -z "$$(docker ps -aq -f name=$(CONTAINER_NAME))" ]; then \ - echo "Stopping and removing existing container..."; \ - docker stop $(CONTAINER_NAME) && docker rm $(CONTAINER_NAME); \ - fi - -clean-images: - @if docker images | grep $(IMAGE); then \ - echo "Removing Docker images for $(IMAGE)..."; \ - docker rmi $(IMAGE):$(TAG) || true; \ - fi diff --git a/docker/postgis/version.yaml b/docker/postgis/version.yaml new file mode 100644 index 000000000..0dd501aaf --- /dev/null +++ b/docker/postgis/version.yaml @@ -0,0 +1,8 @@ +base: postgres +versions: + - 13.3 + - 14.19 + - 15.14 + - 16.10 + - 17.6 + - 18.0