|
| 1 | +# syntax=docker/dockerfile:1.6 |
| 2 | +# Alpine-based slim PostgreSQL 17 image with Nix extensions |
| 3 | +# Fast build variant: uses a BuildKit cache mount to persist the Nix store between builds. |
| 4 | +# First build still downloads packages; subsequent builds skip downloads entirely. |
| 5 | + |
| 6 | +#################### |
| 7 | +# Stage 1: Nix builder |
| 8 | +#################### |
| 9 | +FROM alpine:3.21 AS nix-builder |
| 10 | + |
| 11 | +# Install dependencies for nix installer (coreutils for GNU cp, sudo for installer) |
| 12 | +RUN apk add --no-cache \ |
| 13 | + bash \ |
| 14 | + coreutils \ |
| 15 | + curl \ |
| 16 | + shadow \ |
| 17 | + sudo \ |
| 18 | + xz |
| 19 | + |
| 20 | +# Create users (Alpine syntax) |
| 21 | +RUN addgroup -S postgres && \ |
| 22 | + adduser -S -h /var/lib/postgresql -s /bin/bash -G postgres postgres && \ |
| 23 | + addgroup -S wal-g && \ |
| 24 | + adduser -S -s /bin/bash -G wal-g wal-g |
| 25 | + |
| 26 | +WORKDIR /nixpg |
| 27 | +COPY . . |
| 28 | + |
| 29 | +# Build PostgreSQL and groonga with extensions. |
| 30 | +# |
| 31 | +# --mount=type=cache persists /nix between builds on this machine so Nix does not |
| 32 | +# re-download or rebuild packages that are already in the store. The cache is keyed |
| 33 | +# by id so it is shared across invalidated layers (e.g. when COPY . . changes). |
| 34 | +# |
| 35 | +# Because cache-mount contents are NOT committed to the image layer we copy the |
| 36 | +# finished store to /nix-output at the end; the production stage COPYs from there. |
| 37 | +# |
| 38 | +# Nix is installed in single-user (--no-daemon) mode so the entire store lives |
| 39 | +# under /nix (the cache mount) and needs no daemon process. |
| 40 | +RUN --mount=type=cache,id=psql17-nix-store,target=/nix,sharing=locked \ |
| 41 | + sh -c ' \ |
| 42 | + set -eu; \ |
| 43 | + \ |
| 44 | + # Write nix.conf every time (it lives in the writable layer, not the cache mount). \ |
| 45 | + # build-users-group must be empty so the single-user installer works as root; \ |
| 46 | + # sandbox=false is required inside Docker containers. \ |
| 47 | + mkdir -p /etc/nix; \ |
| 48 | + printf "build-users-group = \nsandbox = false\nextra-experimental-features = nix-command flakes\nextra-substituters = https://nix-postgres-artifacts.s3.amazonaws.com\nextra-trusted-public-keys = nix-postgres-artifacts:dGZlQOvKcNEjvT7QEAJbcV6b6uk7VF/hWMjhYleiaLI=\n" > /etc/nix/nix.conf; \ |
| 49 | + \ |
| 50 | + if [ ! -f /nix/var/nix/profiles/default/bin/nix ]; then \ |
| 51 | + curl -L https://releases.nixos.org/nix/nix-2.33.2/install | sh -s -- --no-daemon --no-channel-add; \ |
| 52 | + fi; \ |
| 53 | + \ |
| 54 | + export PATH="/nix/var/nix/profiles/default/bin:$PATH"; \ |
| 55 | + \ |
| 56 | + nix profile add path:.#psql_17_slim/bin; \ |
| 57 | + nix store gc; \ |
| 58 | + \ |
| 59 | + nix profile add path:.#supabase-groonga; \ |
| 60 | + nix store gc; \ |
| 61 | + \ |
| 62 | + # Copy the store out of the cache mount so it is committed to the image layer. \ |
| 63 | + mkdir -p /nix-output; \ |
| 64 | + cp -a /nix/. /nix-output/; \ |
| 65 | + ' && \ |
| 66 | + mkdir -p /tmp/groonga-plugins && \ |
| 67 | + cp -r /nix-output/var/nix/profiles/default/lib/groonga/plugins /tmp/groonga-plugins/ |
| 68 | + |
| 69 | +#################### |
| 70 | +# Stage 2: Gosu builder |
| 71 | +#################### |
| 72 | +FROM alpine:3.21 AS gosu-builder |
| 73 | + |
| 74 | +ARG TARGETARCH |
| 75 | +ARG GOSU_VERSION=1.16 |
| 76 | + |
| 77 | +RUN apk add --no-cache gnupg curl |
| 78 | + |
| 79 | +# Download and verify gosu |
| 80 | +RUN curl -fsSL "https://github.com/tianon/gosu/releases/download/${GOSU_VERSION}/gosu-${TARGETARCH}" -o /usr/local/bin/gosu && \ |
| 81 | + curl -fsSL "https://github.com/tianon/gosu/releases/download/${GOSU_VERSION}/gosu-${TARGETARCH}.asc" -o /usr/local/bin/gosu.asc && \ |
| 82 | + GNUPGHOME="$(mktemp -d)" && \ |
| 83 | + export GNUPGHOME && \ |
| 84 | + gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4 && \ |
| 85 | + gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu && \ |
| 86 | + rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc && \ |
| 87 | + chmod +x /usr/local/bin/gosu |
| 88 | + |
| 89 | +#################### |
| 90 | +# Stage 3: Final production image |
| 91 | +#################### |
| 92 | +FROM alpine:3.21 AS production |
| 93 | + |
| 94 | +# Install minimal runtime dependencies |
| 95 | +RUN apk add --no-cache \ |
| 96 | + bash \ |
| 97 | + curl \ |
| 98 | + shadow \ |
| 99 | + su-exec \ |
| 100 | + tzdata \ |
| 101 | + musl-locales \ |
| 102 | + musl-locales-lang \ |
| 103 | + && rm -rf /var/cache/apk/* |
| 104 | + |
| 105 | +# Create postgres user/group |
| 106 | +RUN addgroup -S postgres && \ |
| 107 | + adduser -S -G postgres -h /var/lib/postgresql -s /bin/bash postgres && \ |
| 108 | + addgroup -S wal-g && \ |
| 109 | + adduser -S -G wal-g -s /bin/bash wal-g && \ |
| 110 | + adduser postgres wal-g |
| 111 | + |
| 112 | +# Copy Nix store and profiles from builder (written to /nix-output to escape cache mount) |
| 113 | +COPY --from=nix-builder /nix-output /nix |
| 114 | + |
| 115 | +# Copy groonga plugins |
| 116 | +COPY --from=nix-builder /tmp/groonga-plugins/plugins /usr/lib/groonga/plugins |
| 117 | + |
| 118 | +# Copy gosu |
| 119 | +COPY --from=gosu-builder /usr/local/bin/gosu /usr/local/bin/gosu |
| 120 | + |
| 121 | +# Setup PostgreSQL directories |
| 122 | +RUN mkdir -p /usr/lib/postgresql/bin \ |
| 123 | + /usr/lib/postgresql/share/postgresql \ |
| 124 | + /usr/share/postgresql \ |
| 125 | + /var/lib/postgresql/data \ |
| 126 | + /var/run/postgresql \ |
| 127 | + && chown -R postgres:postgres /usr/lib/postgresql \ |
| 128 | + && chown -R postgres:postgres /var/lib/postgresql \ |
| 129 | + && chown -R postgres:postgres /usr/share/postgresql \ |
| 130 | + && chown -R postgres:postgres /var/run/postgresql |
| 131 | + |
| 132 | +# Create symbolic links for binaries |
| 133 | +RUN for f in /nix/var/nix/profiles/default/bin/*; do \ |
| 134 | + ln -sf "$f" /usr/lib/postgresql/bin/ 2>/dev/null || true; \ |
| 135 | + ln -sf "$f" /usr/bin/ 2>/dev/null || true; \ |
| 136 | + done |
| 137 | + |
| 138 | +# Create symbolic links for PostgreSQL shares |
| 139 | +RUN ln -sf /nix/var/nix/profiles/default/share/postgresql/* /usr/lib/postgresql/share/postgresql/ 2>/dev/null || true && \ |
| 140 | + ln -sf /nix/var/nix/profiles/default/share/postgresql/* /usr/share/postgresql/ 2>/dev/null || true && \ |
| 141 | + ln -sf /usr/lib/postgresql/share/postgresql/timezonesets /usr/share/postgresql/timezonesets 2>/dev/null || true |
| 142 | + |
| 143 | +# Set permissions |
| 144 | +RUN chown -R postgres:postgres /usr/lib/postgresql && \ |
| 145 | + chown -R postgres:postgres /usr/share/postgresql |
| 146 | + |
| 147 | +# Setup configs |
| 148 | +COPY --chown=postgres:postgres ansible/files/postgresql_config/postgresql.conf.j2 /etc/postgresql/postgresql.conf |
| 149 | +COPY --chown=postgres:postgres ansible/files/postgresql_config/pg_hba.conf.j2 /etc/postgresql/pg_hba.conf |
| 150 | +COPY --chown=postgres:postgres ansible/files/postgresql_config/pg_ident.conf.j2 /etc/postgresql/pg_ident.conf |
| 151 | +COPY --chown=postgres:postgres ansible/files/postgresql_config/conf.d /etc/postgresql-custom/conf.d |
| 152 | +COPY --chown=postgres:postgres ansible/files/postgresql_config/postgresql-stdout-log.conf /etc/postgresql/logging.conf |
| 153 | +COPY --chown=postgres:postgres ansible/files/postgresql_config/supautils.conf.j2 /etc/postgresql-custom/supautils.conf |
| 154 | +COPY --chown=postgres:postgres ansible/files/postgresql_extension_custom_scripts /etc/postgresql-custom/extension-custom-scripts |
| 155 | +COPY --chown=postgres:postgres ansible/files/pgsodium_getkey_urandom.sh.j2 /usr/lib/postgresql/bin/pgsodium_getkey.sh |
| 156 | +COPY --chown=postgres:postgres ansible/files/postgresql_config/custom_walg.conf /etc/postgresql-custom/wal-g.conf |
| 157 | +COPY --chown=postgres:postgres ansible/files/postgresql_config/custom_read_replica.conf /etc/postgresql-custom/read-replica.conf |
| 158 | +COPY --chown=postgres:postgres ansible/files/walg_helper_scripts/wal_fetch.sh /home/postgres/wal_fetch.sh |
| 159 | +COPY ansible/files/walg_helper_scripts/wal_change_ownership.sh /root/wal_change_ownership.sh |
| 160 | + |
| 161 | +# Configure PostgreSQL settings |
| 162 | +RUN sed -i \ |
| 163 | + -e "s|#unix_socket_directories = '/tmp'|unix_socket_directories = '/var/run/postgresql'|g" \ |
| 164 | + -e "s|#session_preload_libraries = ''|session_preload_libraries = 'supautils'|g" \ |
| 165 | + -e "s|#include = '/etc/postgresql-custom/supautils.conf'|include = '/etc/postgresql-custom/supautils.conf'|g" \ |
| 166 | + -e "s|#include = '/etc/postgresql-custom/wal-g.conf'|include = '/etc/postgresql-custom/wal-g.conf'|g" /etc/postgresql/postgresql.conf && \ |
| 167 | + echo "pgsodium.getkey_script= '/usr/lib/postgresql/bin/pgsodium_getkey.sh'" >> /etc/postgresql/postgresql.conf && \ |
| 168 | + echo "vault.getkey_script= '/usr/lib/postgresql/bin/pgsodium_getkey.sh'" >> /etc/postgresql/postgresql.conf && \ |
| 169 | + chown -R postgres:postgres /etc/postgresql-custom |
| 170 | + |
| 171 | +# Remove timescaledb and plv8 references (not in pg17) |
| 172 | +RUN sed -i 's/ timescaledb,//g;' "/etc/postgresql/postgresql.conf" && \ |
| 173 | + sed -i 's/db_user_namespace = off/#db_user_namespace = off/g;' "/etc/postgresql/postgresql.conf" && \ |
| 174 | + sed -i 's/ timescaledb,//g; s/ plv8,//g' "/etc/postgresql-custom/supautils.conf" |
| 175 | + |
| 176 | +# Include schema migrations |
| 177 | +COPY migrations/db /docker-entrypoint-initdb.d/ |
| 178 | +COPY ansible/files/pgbouncer_config/pgbouncer_auth_schema.sql /docker-entrypoint-initdb.d/init-scripts/00-schema.sql |
| 179 | +COPY ansible/files/stat_extension.sql /docker-entrypoint-initdb.d/migrations/00-extension.sql |
| 180 | + |
| 181 | +# Add entrypoint script |
| 182 | +ADD --chmod=0755 \ |
| 183 | + https://github.com/docker-library/postgres/raw/889f9447cd2dfe21cccfbe9bb7945e3b037e02d8/17/bullseye/docker-entrypoint.sh \ |
| 184 | + /usr/local/bin/docker-entrypoint.sh |
| 185 | + |
| 186 | +# Setup pgsodium key script |
| 187 | +RUN mkdir -p /usr/share/postgresql/extension/ && \ |
| 188 | + ln -s /usr/lib/postgresql/bin/pgsodium_getkey.sh /usr/share/postgresql/extension/pgsodium_getkey && \ |
| 189 | + chmod +x /usr/lib/postgresql/bin/pgsodium_getkey.sh |
| 190 | + |
| 191 | +# Environment variables |
| 192 | +ENV PATH="/nix/var/nix/profiles/default/bin:/usr/lib/postgresql/bin:${PATH}" |
| 193 | +ENV PGDATA=/var/lib/postgresql/data |
| 194 | +ENV POSTGRES_HOST=/var/run/postgresql |
| 195 | +ENV POSTGRES_USER=supabase_admin |
| 196 | +ENV POSTGRES_DB=postgres |
| 197 | +ENV POSTGRES_INITDB_ARGS="--allow-group-access --locale-provider=icu --encoding=UTF-8 --icu-locale=en_US.UTF-8" |
| 198 | +ENV LANG=en_US.UTF-8 |
| 199 | +ENV LANGUAGE=en_US:en |
| 200 | +ENV LC_ALL=en_US.UTF-8 |
| 201 | +ENV GRN_PLUGINS_DIR=/usr/lib/groonga/plugins |
| 202 | +# Point to minimal glibc locales included in slim Nix package for initdb locale support |
| 203 | +ENV LOCALE_ARCHIVE=/nix/var/nix/profiles/default/lib/locale/locale-archive |
| 204 | + |
| 205 | +ENTRYPOINT ["docker-entrypoint.sh"] |
| 206 | +HEALTHCHECK --interval=2s --timeout=2s --retries=10 CMD pg_isready -U postgres -h localhost |
| 207 | +STOPSIGNAL SIGINT |
| 208 | +EXPOSE 5432 |
| 209 | + |
| 210 | +CMD ["postgres", "-D", "/etc/postgresql"] |
0 commit comments