skip to content
alcher.dev

Lith Labs 012: ECR

/ 2 min read

Part of the Lith Labs series

Goal

The goal is to bootstrap an ECR repository and create a script that builds and pushes the app’s Docker image onto the repo.

ECR as part of the Bootstrap module

A key consideration that I made is to determine whether the ECR repository should be part of the main module, or the bootstrap module. I decided that it should be part of the bootstrap module as it’s a “data dependency” that should have a different lifecycle management from the main resources.

And worth noting as I have already encountered this issue before: bootstrapping the ECR repo and running the build script to push an initial image will solve the chicken and egg problem when using ECR images.

Minimizing the Docker image size

It shouldn’t have come as a surprise but my unoptimized Dockerfile is producing a 1GB+ image when built. I’ve managed to drop it down to ~200MB by using multi-stage Docker builds:

FROM python:3.13-slim-bookworm AS builder

WORKDIR /app

ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

RUN pip install --upgrade pip

COPY requirements.in .
RUN pip install --no-cache-dir -r requirements.in

FROM python:3.13-slim-bookworm

WORKDIR /app

RUN apt update && apt -y dist-upgrade
RUN apt install -y netcat-traditional

ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

COPY --from=builder /usr/local/lib/python3.13/site-packages/ /usr/local/lib/python3.13/site-packages/
COPY --from=builder /usr/local/bin/ /usr/local/bin/

COPY . .

ENTRYPOINT ["/app/entrypoint.sh"]

Note: I probably could go a bit more lower by figuring out a way to ditch nc (netcat), but at this point I don’t see it as that big of a problem.

Utilizing jq to parse JSON

Before I came across jq, I usually write Python scripts to pull out values from a JSON file. With jq, most of the solution that I need are one-liners that I can do straight in shell scripts (which I use to call the aforementioned Python scripts anyway).

This results in way less code for basic JSON parsing. See this example for inspecting the Terraform output values to determine where to push the built image:

ECR_REPO_URL=$(jq -r .ecr_repo_url.value < tf-output.json)
ECR_URL=$(echo "$ECR_REPO_URL" | cut -d "/" -f 1)
ECR_REPO_NAME=$(echo "$ECR_REPO_URL" | cut -d "/" -f 2)
ECR_REPO_IMAGE_TAG="latest"

# ...
docker build -t "$ECR_REPO_NAME:$ECR_REPO_IMAGE_TAG" -t "$ECR_REPO_URL:$ECR_REPO_IMAGE_TAG" .
docker push "$ECR_REPO_URL:$ECR_REPO_IMAGE_TAG"

Pretty neat!

Conclusion

In this lab, I created an ECR repository as part of the bootstrapping process and wrote a build script that build+push the application image to the repo.

The source is available at the feature/012-ecr branch.