Search code examples
dockeralpine-linuxdocker-multi-stage-build

using apk packages that were installed in a builder image in an other image


I have a (multi-stage) alpine builder image that installs a few apk packages (postgres) and a Go application. I then have another image that extends from this image and needs to use the apk packages installed in it. However it seems the apk packages are not in the final image. I built the builder image and ran it and the apk packages are not installed in there either...

The builder:

FROM golang:1.18-alpine AS base
RUN apk add --no-cache --quiet musl-dev git gcc upx postgresql-client

RUN LDFLAGS="-s -w -linkmode external -extldflags \"-static\"" &&\
    go install --ldflags "$LDFLAGS" github.com/GoogleCloudPlatform/cloudsql-proxy/cmd/cloud_sql_proxy@latest &&\
    upx /go/bin/cloud_sql_proxy

# Build the dist image
FROM alpine
COPY --from=base /go/bin/cloud_sql_proxy /

final image:

# syntax=docker/dockerfile:experimental

# set default PSQLCONNECTOR_VERSION value
ARG PSQLCONNECTOR_VERSION=20220425

# pull from base image
FROM gcr.io/mycrepo/psql-cloudproxy-connector:$PSQLCONNECTOR_VERSION-alpine AS base

# copy script
COPY ./some_script.sh /

# make the script executable
RUN ["chmod", "+x", "/some_script.sh"]

some_script.sh contains:

PG_URL="postgresql://postgres:password@localhost:5432/some_db?sslmode=disable"
psql "$PG_URL" -c "SELECT * FROM ...;"

psql (from postgresql-client pkg) is not present in the second image. When the script is run, we get /some_script.sh: line 35: psql: not found.

here is the output of apk list from within the final container:

apk list
WARNING: Ignoring https://dl-cdn.alpinelinux.org/alpine/v3.15/main: No such file or directory
WARNING: Ignoring https://dl-cdn.alpinelinux.org/alpine/v3.15/community: No such file or directory
libretls-3.3.4-r3 x86_64 {libretls} (ISC AND (BSD-3-Clause OR MIT)) [installed]
musl-1.2.2-r7 x86_64 {musl} (MIT) [installed]
zlib-1.2.12-r0 x86_64 {zlib} (Zlib) [installed]
apk-tools-2.12.7-r3 x86_64 {apk-tools} (GPL-2.0-only) [installed]
musl-utils-1.2.2-r7 x86_64 {musl} (MIT BSD GPL2+) [installed]
libssl1.1-1.1.1n-r0 x86_64 {openssl} (OpenSSL) [installed]
alpine-baselayout-3.2.0-r18 x86_64 {alpine-baselayout} (GPL-2.0-only) [installed]
alpine-keys-2.4-r1 x86_64 {alpine-keys} (MIT) [installed]
busybox-1.34.1-r5 x86_64 {busybox} (GPL-2.0-only) [installed]
scanelf-1.3.3-r0 x86_64 {pax-utils} (GPL-2.0-only) [installed]
ca-certificates-bundle-20211220-r0 x86_64 {ca-certificates} (MPL-2.0 AND MIT) [installed]
libc-utils-0.7.2-r3 x86_64 {libc-dev} (BSD-2-Clause AND BSD-3-Clause) [installed]
ssl_client-1.34.1-r5 x86_64 {busybox} (GPL-2.0-only) [installed]
libcrypto1.1-1.1.1n-r0 x86_64 {openssl} (OpenSSL) [installed]

I was under the impression that building an image A that is extending from an image B with installed apk packages would give an image A with the packages installed in image B. But it seems the installed packages are not even shared between multiple stages in the same Dockerfile?

What is the correct way to copy these packages to the final image A?

ps. The images need to be separate because I need to build them at different times, so a single multi-stage build is not useful


Solution

  • A second FROM statement in a Dockerfile completely resets the image to the new base image. Nothing is carried over. The only way to get something from an earlier stage is the COPY --from=... statement.

    If you need some packages in your final image, you should install them in the final image. Copying them from an earlier stage will be a difficult, since you'll need to know what files the package installed.

    In your case, you should install the packages in the alpine image. Then they'll also be available in the final image you make, that you base on the alpine image.