Skip to content

Commit 4cbcdcd

Browse files
committed
build-images: share one container build across multiple tags
Previously each tag invoked a fresh `dagger -c` session, so non-deterministic build steps could produce different image digests per tag. Build the container once per image and reuse it for every tag — via a new `publish-multi` Headway function for the publish path, and `docker tag` after a single `export-image` for the local-export path.
1 parent a4da2e4 commit 4cbcdcd

2 files changed

Lines changed: 74 additions & 31 deletions

File tree

bin/build-images

Lines changed: 56 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -77,21 +77,27 @@ function build_image() {
7777
echo "missing 'container' arg"
7878
exit 1
7979
fi
80-
local tag=$3
81-
if [ -z "$tag" ]; then
82-
echo "missing 'tag' arg"
80+
local addresses=$3
81+
if [ -z "$addresses" ]; then
82+
echo "missing 'addresses' arg"
8383
exit 1
8484
fi
8585

86-
HEADWAY_IMAGE_REPO="${HEADWAY_IMAGE_REPO:-ghcr.io/headwaymaps}"
87-
local image_ref="${HEADWAY_IMAGE_REPO}/${container}:${tag}"
88-
8986
if [ "$PUBLISH" = true ]; then
90-
echo "Publishing ${image_ref}..."
91-
dagger -c "${build_function} | publish \"${image_ref}\""
87+
echo "Publishing ${container}: ${addresses}"
88+
dagger -c "publish-multi --container=\$(${build_function}) --addresses=${addresses}"
9289
else
93-
echo "Exporting ${image_ref} to local docker..."
94-
dagger --interactive -c "${build_function} | export-image \"${image_ref}\""
90+
# Dagger's host-image-loader can only fulfill one ExportImage per session,
91+
# so export to the first tag and use `docker tag` for the rest. This keeps
92+
# all tags pointing at the same image id locally.
93+
IFS=',' read -ra _addr_list <<< "$addresses"
94+
local primary="${_addr_list[0]}"
95+
echo "Exporting ${container} to local docker: ${primary}"
96+
dagger --interactive -c "${build_function} | export-image \"${primary}\""
97+
for extra in "${_addr_list[@]:1}"; do
98+
echo "Tagging ${extra} -> ${primary}"
99+
docker tag "$primary" "$extra"
100+
done
95101
fi
96102
}
97103

@@ -108,30 +114,49 @@ function build_image_tags() {
108114
web_branding_param="--branding=$BRANDING"
109115
fi
110116

111-
# Convert comma/space separated tags to array
112-
IFS=', ' read -ra tag_array <<< "$tags_input"
117+
HEADWAY_IMAGE_REPO="${HEADWAY_IMAGE_REPO:-ghcr.io/headwaymaps}"
113118

119+
# Convert comma/space separated tags to array, dropping empties
120+
IFS=', ' read -ra tag_array <<< "$tags_input"
121+
local tags=()
114122
for tag in "${tag_array[@]}"; do
115-
# Skip empty tags
116-
if [ -z "$tag" ]; then
117-
continue
118-
fi
119-
120-
echo "Processing tag: $tag"
121-
122-
should_build "frontend" && build_image "web-serve-container ${web_branding_param}" "headway" "$tag"
123-
should_build "frontend-init" && build_image "web-init-container" "headway-init" "$tag"
124-
should_build "otp-init" && build_image "otp-init-container" "opentripplanner-init" "$tag"
125-
should_build "otp" && build_image "otp-serve-container" "opentripplanner" "$tag"
126-
should_build "pelias-init" && build_image "pelias-init-container" "pelias-init" "$tag"
127-
should_build "tileserver" && build_image "tileserver-serve-container" "tileserver" "$tag"
128-
should_build "tileserver-init" && build_image "tileserver-init-container" "tileserver-init" "$tag"
129-
should_build "travelmux-init" && build_image "travelmux-init-container" "travelmux-init" "$tag"
130-
should_build "travelmux" && build_image "travelmux-serve-container" "travelmux" "$tag"
131-
should_build "valhalla-init" && build_image "valhalla-init-container" "valhalla-init" "$tag"
132-
should_build "valhalla" && build_image "valhalla-serve-container" "valhalla" "$tag"
133-
true
123+
[ -n "$tag" ] && tags+=("$tag")
134124
done
125+
126+
if [ ${#tags[@]} -eq 0 ]; then
127+
echo "no tags provided"
128+
exit 1
129+
fi
130+
131+
# Build comma-separated "repo/container:tag" list for one image across all tags.
132+
function addresses_for() {
133+
local container=$1
134+
local out=""
135+
for tag in "${tags[@]}"; do
136+
out+="${HEADWAY_IMAGE_REPO}/${container}:${tag},"
137+
done
138+
echo "${out%,}"
139+
}
140+
141+
function build() {
142+
local filter=$1
143+
local build_function=$2
144+
local container=$3
145+
should_build "$filter" || return 0
146+
build_image "$build_function" "$container" "$(addresses_for "$container")"
147+
}
148+
149+
build "frontend" "web-serve-container ${web_branding_param}" "headway"
150+
build "frontend-init" "web-init-container" "headway-init"
151+
build "otp-init" "otp-init-container" "opentripplanner-init"
152+
build "otp" "otp-serve-container" "opentripplanner"
153+
build "pelias-init" "pelias-init-container" "pelias-init"
154+
build "tileserver" "tileserver-serve-container" "tileserver"
155+
build "tileserver-init" "tileserver-init-container" "tileserver-init"
156+
build "travelmux-init" "travelmux-init-container" "travelmux-init"
157+
build "travelmux" "travelmux-serve-container" "travelmux"
158+
build "valhalla-init" "valhalla-init-container" "valhalla-init"
159+
build "valhalla" "valhalla-serve-container" "valhalla"
135160
}
136161

137162
echo "Mode: $([ "$PUBLISH" = true ] && echo "Publish" || echo "Export")"

dagger/main.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,24 @@ func WithAptPackages(container *dagger.Container, packages ...string) *dagger.Co
568568
return container.WithExec([]string{"sh", "-c", cmd})
569569
}
570570

571+
// Publish a container to multiple registry addresses in a single Dagger
572+
// session, so every tag points at the same image digest.
573+
func (h *Headway) PublishMulti(
574+
ctx context.Context,
575+
container *dagger.Container,
576+
addresses []string,
577+
) ([]string, error) {
578+
out := make([]string, 0, len(addresses))
579+
for _, addr := range addresses {
580+
ref, err := container.Publish(ctx, addr)
581+
if err != nil {
582+
return nil, fmt.Errorf("publishing %q: %w", addr, err)
583+
}
584+
out = append(out, ref)
585+
}
586+
return out, nil
587+
}
588+
571589
func compressDir(dir *dagger.Directory) *dagger.File {
572590
container := slimContainer("zstd").
573591
WithExec([]string{"mkdir", "/app"}).

0 commit comments

Comments
 (0)