Skip to content

Commit c9109ef

Browse files
authored
(locally) Build & push lading container script (#1827)
### What does this PR do? Adds a `ci/container-build-and-push` script for building and pushing lading Docker images locally, targeting the `lading-dev` ECR repository to avoid accidental overwrites of released images. Also updates the Dockerfile to work without sccache and bumps `libssl-dev`. ### Motivation Enable local and agentic-workflow Docker builds of lading without depending on the full CI pipeline. Uses a separate `lading-dev` ECR repo so dev builds are isolated from production releases. ### Related issues - SMP PR: DataDog/single-machine-performance#4183 (creates `lading-dev` ECR repo) ### Additional Notes - Pushes to `lading-dev` ECR (not `lading`) per reviewer feedback to prevent accidental overwrites of released versions - AWS prereqs (`aws`, `aws-vault`, `jq`) are only required when pushing; `--no-push` only needs `docker` - Multi-arch `--no-push` builds stay in cache only — use a single `--arch` for a loadable local image - sccache is now conditional on `SCCACHE_BUCKET` being set, so local builds skip it - `libssl-dev` pinned version bumped from `3.0.17-1~deb12u3` to `3.0.18-1~deb12u2` - Builds both amd64+arm64 in ~8 min on a laptop without sccache
1 parent 3febd2b commit c9109ef

File tree

2 files changed

+205
-2
lines changed

2 files changed

+205
-2
lines changed

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ RUN --mount=type=secret,id=aws_access_key_id \
4343
export AWS_ACCESS_KEY_ID=$(cat /run/secrets/aws_access_key_id) && \
4444
export AWS_SECRET_ACCESS_KEY=$(cat /run/secrets/aws_secret_access_key) && \
4545
export AWS_SESSION_TOKEN=$(cat /run/secrets/aws_session_token) && \
46-
export RUSTC_WRAPPER=sccache && \
46+
if [ -n "${SCCACHE_BUCKET:-}" ]; then export RUSTC_WRAPPER=sccache; fi && \
4747
cargo chef cook --release --locked --features logrotate_fs --recipe-path recipe.json
4848

4949
# Stage 2: Builder - Build source code
@@ -74,7 +74,7 @@ RUN --mount=type=secret,id=aws_access_key_id \
7474
export AWS_ACCESS_KEY_ID=$(cat /run/secrets/aws_access_key_id) && \
7575
export AWS_SECRET_ACCESS_KEY=$(cat /run/secrets/aws_secret_access_key) && \
7676
export AWS_SESSION_TOKEN=$(cat /run/secrets/aws_session_token) && \
77-
export RUSTC_WRAPPER=sccache && \
77+
if [ -n "${SCCACHE_BUCKET:-}" ]; then export RUSTC_WRAPPER=sccache; fi && \
7878
cargo build --release --locked --bin lading --features logrotate_fs
7979

8080
# Stage 3: Runtime

ci/container-build-and-push

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
#!/usr/bin/env bash
2+
set -o errexit
3+
set -o nounset
4+
set -o pipefail
5+
6+
ECR_REGISTRY="850406765696.dkr.ecr.us-west-2.amazonaws.com"
7+
ECR_REPO="${ECR_REGISTRY}/lading-dev"
8+
AWS_VAULT_PROFILE="smp"
9+
10+
usage() {
11+
cat <<EOF
12+
Usage: ci/container-build-and-push [OPTIONS]
13+
14+
Build and push lading Docker images to ECR.
15+
16+
Options:
17+
--tag TAG Image tag (default: git commit SHA)
18+
--arch ARCH Architecture(s): amd64, arm64, or amd64,arm64 (default: amd64,arm64)
19+
--profile PROFILE aws-vault profile (default: smp)
20+
--random-tag Use a random UUID as the image tag
21+
--no-push Build only, don't push to ECR
22+
-h, --help Show this help
23+
24+
Examples:
25+
ci/container-build-and-push # build both amd64+arm64, tag from git SHA
26+
ci/container-build-and-push --tag v0.32.0 # explicit tag
27+
ci/container-build-and-push --arch amd64,arm64 # multi-arch build
28+
ci/container-build-and-push --no-push # build only
29+
EOF
30+
}
31+
32+
# Parse arguments
33+
TAG=""
34+
RANDOM_TAG=false
35+
ARCH=""
36+
PUSH=true
37+
38+
while [[ $# -gt 0 ]]; do
39+
case "$1" in
40+
--tag) TAG="$2"; shift 2 ;;
41+
--arch) ARCH="$2"; shift 2 ;;
42+
--profile) AWS_VAULT_PROFILE="$2"; shift 2 ;;
43+
--random-tag) RANDOM_TAG=true; shift ;;
44+
--no-push) PUSH=false; shift ;;
45+
-h|--help) usage; exit 0 ;;
46+
*) echo "Error: Unknown option: $1" >&2; usage >&2; exit 1 ;;
47+
esac
48+
done
49+
50+
if [[ -n "$TAG" && "$RANDOM_TAG" == "true" ]]; then
51+
echo "Error: --tag and --random-tag are mutually exclusive" >&2
52+
exit 1
53+
fi
54+
55+
if [[ "$RANDOM_TAG" == "true" ]]; then
56+
TAG="$(uuidgen | tr '[:upper:]' '[:lower:]')"
57+
echo "Generated random tag: ${TAG}"
58+
fi
59+
60+
# Check prerequisites
61+
REQUIRED_CMDS=(docker)
62+
if [[ "$PUSH" == "true" ]]; then
63+
REQUIRED_CMDS+=(aws aws-vault jq)
64+
fi
65+
for cmd in "${REQUIRED_CMDS[@]}"; do
66+
if ! command -v "$cmd" &>/dev/null; then
67+
echo "Error: $cmd is not installed"
68+
exit 1
69+
fi
70+
done
71+
72+
# Ensure Docker daemon is running
73+
if ! docker info &>/dev/null; then
74+
echo "Error: Docker daemon is not running. Start Docker Desktop and try again."
75+
exit 1
76+
fi
77+
78+
# Ensure buildx is available
79+
if ! docker buildx version &>/dev/null; then
80+
echo "Error: Docker Buildx is not available"
81+
exit 1
82+
fi
83+
84+
# Check Docker has enough memory for release builds with LTO
85+
MIN_MEMORY_GB=16
86+
DOCKER_MEM_BYTES=$(docker info --format '{{.MemTotal}}' 2>/dev/null)
87+
DOCKER_MEM_GB=$(( DOCKER_MEM_BYTES / 1073741824 ))
88+
if [[ "$DOCKER_MEM_GB" -lt "$MIN_MEMORY_GB" ]]; then
89+
echo "Error: Docker has ${DOCKER_MEM_GB} GB of memory, but at least ${MIN_MEMORY_GB} GB is required"
90+
echo "Increase memory in Docker Desktop -> Settings -> Resources"
91+
exit 1
92+
fi
93+
echo "Docker memory: ${DOCKER_MEM_GB} GB"
94+
95+
# Default to both architectures if not specified
96+
if [[ -z "$ARCH" ]]; then
97+
ARCH="amd64,arm64"
98+
echo "Defaulting to both architectures: ${ARCH}"
99+
fi
100+
101+
# Determine tag
102+
if [[ -z "$TAG" ]]; then
103+
TAG="sha-$(git rev-parse HEAD)"
104+
echo "Using git SHA tag: ${TAG}"
105+
fi
106+
107+
# Parse architectures
108+
IFS=',' read -ra ARCHS <<< "$ARCH"
109+
MULTI_ARCH=false
110+
if [[ ${#ARCHS[@]} -gt 1 ]]; then
111+
MULTI_ARCH=true
112+
fi
113+
114+
# Build platform string
115+
PLATFORMS=""
116+
for a in "${ARCHS[@]}"; do
117+
if [[ -n "$PLATFORMS" ]]; then
118+
PLATFORMS="${PLATFORMS},"
119+
fi
120+
PLATFORMS="${PLATFORMS}linux/${a}"
121+
done
122+
123+
# Set up QEMU for cross-arch builds
124+
if [[ "$MULTI_ARCH" == "true" ]]; then
125+
echo "Setting up QEMU for multi-arch builds..."
126+
docker run --rm --privileged tonistiigi/binfmt --install all >/dev/null 2>&1 || true
127+
fi
128+
129+
# Authenticate to ECR using a temporary docker config to avoid macOS credsStore issues.
130+
# We copy the real docker config (minus credsStore) and symlink cli-plugins so that
131+
# docker login can write credentials as plain JSON and buildx still works.
132+
DOCKER_CONFIG_DIR=""
133+
cleanup() {
134+
if [[ -n "$DOCKER_CONFIG_DIR" ]]; then
135+
rm -rf "$DOCKER_CONFIG_DIR"
136+
fi
137+
}
138+
trap cleanup EXIT
139+
140+
if [[ "$PUSH" == "true" ]]; then
141+
echo "Authenticating to ECR..."
142+
REAL_DOCKER_CONFIG="${DOCKER_CONFIG:-${HOME}/.docker}"
143+
DOCKER_CONFIG_DIR="$(mktemp -d)"
144+
# Copy config without credsStore/credHelpers so docker login writes auth as plain JSON
145+
jq 'del(.credsStore, .credHelpers)' "${REAL_DOCKER_CONFIG}/config.json" \
146+
> "${DOCKER_CONFIG_DIR}/config.json"
147+
# Symlink subdirectories so buildx CLI plugin and Docker contexts are found
148+
for subdir in cli-plugins contexts; do
149+
if [[ -d "${REAL_DOCKER_CONFIG}/${subdir}" ]]; then
150+
ln -s "${REAL_DOCKER_CONFIG}/${subdir}" "${DOCKER_CONFIG_DIR}/${subdir}"
151+
fi
152+
done
153+
aws-vault exec "$AWS_VAULT_PROFILE" -- \
154+
aws ecr get-login-password --region us-west-2 \
155+
| DOCKER_CONFIG="$DOCKER_CONFIG_DIR" docker login --username AWS --password-stdin "$ECR_REGISTRY"
156+
export DOCKER_CONFIG="$DOCKER_CONFIG_DIR"
157+
fi
158+
159+
# Create/use a buildx builder that supports multi-platform
160+
BUILDER_NAME="lading-builder"
161+
if ! docker buildx inspect "$BUILDER_NAME" &>/dev/null; then
162+
echo "Creating buildx builder: ${BUILDER_NAME}"
163+
docker buildx create --name "$BUILDER_NAME" --driver docker-container --use
164+
else
165+
docker buildx use "$BUILDER_NAME"
166+
fi
167+
168+
# Build image tags
169+
BUILD_TAGS=("--tag" "${ECR_REPO}:${TAG}")
170+
171+
PUSH_FLAG=""
172+
if [[ "$PUSH" == "true" ]]; then
173+
PUSH_FLAG="--push"
174+
elif [[ "$MULTI_ARCH" != "true" ]]; then
175+
PUSH_FLAG="--load"
176+
else
177+
echo "Warning: multi-arch --no-push builds remain in cache only (use --arch amd64 or --arch arm64 for a loadable image)"
178+
fi
179+
180+
# Build (multi-arch builds both platforms in parallel within a single buildx invocation)
181+
echo "Building for platform(s): ${PLATFORMS}"
182+
echo "Tag: ${TAG}"
183+
184+
docker buildx build \
185+
--platform "$PLATFORMS" \
186+
--build-arg SCCACHE_BUCKET="" \
187+
--build-arg SCCACHE_REGION="" \
188+
--build-arg AWS_ACCESS_KEY_ID="" \
189+
--build-arg AWS_SECRET_ACCESS_KEY="" \
190+
--build-arg AWS_SESSION_TOKEN="" \
191+
--secret "id=aws_access_key_id,src=/dev/null" \
192+
--secret "id=aws_secret_access_key,src=/dev/null" \
193+
--secret "id=aws_session_token,src=/dev/null" \
194+
"${BUILD_TAGS[@]}" \
195+
${PUSH_FLAG} \
196+
-f Dockerfile \
197+
.
198+
199+
if [[ "$PUSH" == "true" ]]; then
200+
echo "Pushed to ${ECR_REPO}:${TAG}"
201+
else
202+
echo "Build complete (not pushed)"
203+
fi

0 commit comments

Comments
 (0)