1- # Builder stage - compile Python packages with C extensions
2- FROM alpine:3.21 AS builder
1+ # tfroot-runner — GitHub ARC runner image preloaded with the OpenTofu IaC
2+ # toolchain. Self-registers with the gha-runner-scale-set listener; jobs run
3+ # directly in the pod (no nested `container:` block).
4+ #
5+ # Layout follows the hatch1fy/infra-images/terraform-runner pattern:
6+ # Stage 1 (tools) — Ubuntu builder for binary downloads + Python venv
7+ # Stage 2 (final) — ghcr.io/actions/actions-runner base + runtime deps
38
4- # hadolint ignore=DL3018
5- RUN apk add --no-cache \
6- python3 py3-pip python3-dev \
7- build-base libffi-dev git
8-
9- # Install Python packages that need compilation
10- ARG CHECKOV_VERSION=3.2.525
11- ARG PRECOMMIT_VERSION=4.6.0
12- RUN pip install --no-cache-dir --break-system-packages --root=/install --prefix=/usr \
13- pre-commit==${PRECOMMIT_VERSION} checkov==${CHECKOV_VERSION}
14-
15- # Final stage
16- FROM alpine:3.21
17-
18- LABEL description="Alpine-based IaC runner for OpenTofu/Terraform on AMD64 architecture."
19-
20- # Install runtime dependencies
21- # cdrkit provides genisoimage equivalent (mkisofs)
22- # binutils provides strip for binary size reduction
23- # hadolint ignore=DL3018
24- RUN apk add --no-cache \
25- curl unzip gnupg \
26- python3 py3-pip libffi libstdc++ \
27- git jq yq \
28- nodejs npm \
29- ansible-core \
30- openssh-client \
31- cdrkit \
32- bash \
33- binutils \
34- make
35-
36- # Copy Python packages from builder
37- COPY --from=builder /install /
38-
39- # Tool versions
9+ # #############################
10+ # Pinned versions — update here
11+ # #############################
4012ARG OPENTOFU_VERSION=1.11.6
4113ARG SOPS_VERSION=3.12.2
4214ARG TERRAFORM_DOCS_VERSION=0.22.0
@@ -45,70 +17,151 @@ ARG HCLEDIT_VERSION=0.2.17
4517ARG TFLINT_VERSION=0.62.0
4618ARG INFRACOST_VERSION=0.10.44
4719ARG KUBECTL_VERSION=1.36.0
20+ ARG KUSTOMIZE_VERSION=5.8.0
21+ ARG CHECKOV_VERSION=3.2.525
22+ ARG PRECOMMIT_VERSION=4.6.0
23+ ARG PYTHON_VERSION=3.14
24+
25+ # #############################
26+ # Stage 1: Build/download tools
27+ # #############################
28+ FROM --platform=linux/amd64 ubuntu:24.04 AS tools
4829
49- # Install all binary tools in a single layer, strip debug symbols, clean up
30+ ARG OPENTOFU_VERSION
31+ ARG SOPS_VERSION
32+ ARG TERRAFORM_DOCS_VERSION
33+ ARG TFUPDATE_VERSION
34+ ARG HCLEDIT_VERSION
35+ ARG TFLINT_VERSION
36+ ARG INFRACOST_VERSION
37+ ARG KUBECTL_VERSION
38+ ARG KUSTOMIZE_VERSION
39+ ARG CHECKOV_VERSION
40+ ARG PRECOMMIT_VERSION
41+ ARG PYTHON_VERSION
42+
43+ ENV DEBIAN_FRONTEND=noninteractive
44+
45+ # Build-time dependencies (Python + add-apt-repository)
46+ # hadolint ignore=DL3008
47+ RUN apt-get update && apt-get install -y --no-install-recommends \
48+ ca-certificates curl gnupg lsb-release software-properties-common unzip \
49+ && add-apt-repository -y ppa:deadsnakes/ppa \
50+ && apt-get update && apt-get install -y --no-install-recommends \
51+ python${PYTHON_VERSION} python${PYTHON_VERSION}-venv python${PYTHON_VERSION}-dev \
52+ build-essential libffi-dev git \
53+ && rm -rf /var/lib/apt/lists/*
54+
55+ # Python venv with the pip-managed tools.
56+ RUN python${PYTHON_VERSION} -m venv /opt/venv \
57+ && /opt/venv/bin/pip install --no-cache-dir \
58+ pre-commit==${PRECOMMIT_VERSION} \
59+ checkov==${CHECKOV_VERSION}
60+
61+ # Binary downloads — single layer, strip debug symbols at the end.
5062# hadolint ignore=DL3003,DL4006
5163RUN set -eux; \
52- # SOPS
53- curl -Lo /usr/local/bin/sops \
54- "https://github.com/getsops/sops/releases/download/v${SOPS_VERSION}/sops-v${SOPS_VERSION}.linux.amd64" ; \
55- chmod +x /usr/local/bin/sops; \
56- # OpenTofu (and symlink as terraform)
57- curl --proto '=https' --tlsv1.2 -fsSL https://get.opentofu.org/install-opentofu.sh | sh -s -- --install-method standalone --opentofu-version "${OPENTOFU_VERSION}" ; \
64+ # OpenTofu
65+ curl --proto '=https' --tlsv1.2 -fsSL https://get.opentofu.org/install-opentofu.sh \
66+ | sh -s -- --install-method standalone --opentofu-version "${OPENTOFU_VERSION}" ; \
67+ # Symlink as terraform for tooling that hardcodes that name.
5868 ln -s /usr/local/bin/tofu /usr/local/bin/terraform; \
69+ # SOPS
70+ curl -fsSL "https://github.com/getsops/sops/releases/download/v${SOPS_VERSION}/sops-v${SOPS_VERSION}.linux.amd64" \
71+ -o /usr/local/bin/sops && chmod +x /usr/local/bin/sops; \
5972 # kubectl
60- curl -fsSL "https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl" -o /usr/local/bin/kubectl; \
61- chmod +x /usr/local/bin/kubectl; \
62- # Kustomize (script outputs to current directory)
63- cd /tmp && curl -s "https://raw.githubusercontent. com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash; \
64- mv /tmp/kustomize /usr/local/bin/ ; \
73+ curl -fsSL "https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl" \
74+ -o /usr/local/bin/kubectl && chmod +x /usr/local/bin/kubectl; \
75+ # kustomize
76+ curl -fsSL "https://github. com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv${KUSTOMIZE_VERSION}/kustomize_v${KUSTOMIZE_VERSION}_linux_amd64.tar.gz" \
77+ | tar xz -C /usr/local/bin kustomize ; \
6578 # terraform-docs
66- curl -sSL "https://terraform-docs.io/dl/v${TERRAFORM_DOCS_VERSION}/terraform-docs-v${TERRAFORM_DOCS_VERSION}-linux-amd64.tar.gz" | \
67- tar xz -C /usr/local/bin terraform-docs; \
68- chmod +x /usr/local/bin/terraform-docs; \
79+ curl -fsSL "https://terraform-docs.io/dl/v${TERRAFORM_DOCS_VERSION}/terraform-docs-v${TERRAFORM_DOCS_VERSION}-linux-amd64.tar.gz" \
80+ | tar xz -C /usr/local/bin terraform-docs && chmod +x /usr/local/bin/terraform-docs; \
6981 # tfupdate
70- curl -sSL "https://github.com/minamijoyo/tfupdate/releases/download/v${TFUPDATE_VERSION}/tfupdate_${TFUPDATE_VERSION}_linux_amd64.tar.gz" | \
71- tar xz -C /usr/local/bin tfupdate; \
72- chmod +x /usr/local/bin/tfupdate; \
82+ curl -fsSL "https://github.com/minamijoyo/tfupdate/releases/download/v${TFUPDATE_VERSION}/tfupdate_${TFUPDATE_VERSION}_linux_amd64.tar.gz" \
83+ | tar xz -C /usr/local/bin tfupdate && chmod +x /usr/local/bin/tfupdate; \
7384 # hcledit
74- curl -sSL "https://github.com/minamijoyo/hcledit/releases/download/v${HCLEDIT_VERSION}/hcledit_${HCLEDIT_VERSION}_linux_amd64.tar.gz" | \
75- tar xz -C /usr/local/bin hcledit; \
76- chmod +x /usr/local/bin/hcledit; \
85+ curl -fsSL "https://github.com/minamijoyo/hcledit/releases/download/v${HCLEDIT_VERSION}/hcledit_${HCLEDIT_VERSION}_linux_amd64.tar.gz" \
86+ | tar xz -C /usr/local/bin hcledit && chmod +x /usr/local/bin/hcledit; \
7787 # tflint
78- curl -s https://raw.githubusercontent.com/terraform-linters/tflint/master/install_linux.sh | TFLINT_VERSION="v${TFLINT_VERSION}" bash; \
88+ curl -fsSL "https://github.com/terraform-linters/tflint/releases/download/v${TFLINT_VERSION}/tflint_linux_amd64.zip" \
89+ -o /tmp/tflint.zip && unzip /tmp/tflint.zip -d /usr/local/bin/ && rm -f /tmp/tflint.zip; \
7990 # infracost
80- curl -fsSL https://raw.githubusercontent. com/infracost/infracost/master/scripts/install.sh | INFRACOST_VERSION= " v${INFRACOST_VERSION}" sh; \
81- # Strip debug symbols from all Go/Rust binaries
82- strip /usr/local/bin/sops \
83- /usr/local/bin/tofu \
91+ curl -fsSL " https://github. com/infracost/infracost/releases/download/ v${INFRACOST_VERSION}/infracost-linux-amd64.tar.gz" \
92+ | tar xz -C /tmp && mv /tmp/infracost-linux-amd64 /usr/local/bin/infracost && chmod +x /usr/local/bin/infracost; \
93+ strip /usr/local/bin/tofu \
94+ /usr/local/bin/sops \
8495 /usr/local/bin/kubectl \
8596 /usr/local/bin/kustomize \
8697 /usr/local/bin/terraform-docs \
8798 /usr/local/bin/tfupdate \
8899 /usr/local/bin/hcledit \
89100 /usr/local/bin/tflint \
90- /usr/local/bin/infracost 2>/dev/null || true; \
91- # Clean up
92- rm -rf /tmp/* /var/cache/apk/*
93-
94- # Remove binutils (only needed for strip)
95- # hadolint ignore=DL3018
96- RUN apk del binutils
97-
98- # Configure git safe.directory globally to handle mounted workspaces
99- # Set in both system and user config for maximum compatibility
100- ENV HOME=/root
101- RUN git config --system --add safe.directory '*' && \
102- git config --global --add safe.directory '*'
103-
104- # Pre-cache pre-commit hooks
105- WORKDIR /pre-commit-init
106- COPY pre-commit-config.yaml .pre-commit-config.yaml
107- RUN git init && \
108- git config user.email "builder@localhost" && \
109- git config user.name "Builder" && \
110- pre-commit install-hooks && \
111- rm -rf /tmp/*
112-
113- WORKDIR /
114- CMD ["/bin/bash" ]
101+ /usr/local/bin/infracost 2>/dev/null || true
102+
103+ # #############################
104+ # Stage 2: Final runner image
105+ # #############################
106+ FROM --platform=linux/amd64 ghcr.io/actions/actions-runner:latest
107+
108+ ARG PYTHON_VERSION
109+
110+ LABEL org.opencontainers.image.title="tfroot-runner" \
111+ org.opencontainers.image.description="GitHub ARC runner with OpenTofu, kubectl, kustomize, sops, ansible, pre-commit, and friends." \
112+ org.opencontainers.image.source="https://github.com/makeitworkcloud/images"
113+
114+ USER root
115+
116+ ENV DEBIAN_FRONTEND=noninteractive
117+
118+ # Runtime dependencies: Python (no -dev), ansible, openssh-client, jq/yq,
119+ # genisoimage (cdrkit equivalent), gnupg, make, shellcheck, libatomic1.
120+ # libatomic1 is needed by the Node.js binary pre-commit downloads for some hooks.
121+ # hadolint ignore=DL3008
122+ RUN apt-get update && apt-get install -y --no-install-recommends \
123+ ca-certificates curl unzip software-properties-common \
124+ && add-apt-repository -y ppa:deadsnakes/ppa \
125+ && apt-get update && apt-get install -y --no-install-recommends \
126+ python${PYTHON_VERSION} python${PYTHON_VERSION}-venv \
127+ ansible-core openssh-client \
128+ jq genisoimage gnupg make shellcheck libatomic1 \
129+ && update-alternatives --install /usr/bin/python3 python3 /usr/bin/python${PYTHON_VERSION} 1 \
130+ && curl -fsSL https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 \
131+ -o /usr/local/bin/yq && chmod +x /usr/local/bin/yq \
132+ && apt-get clean \
133+ && rm -rf /var/lib/apt/lists/* /usr/share/doc/* /usr/share/man/*
134+
135+ # Copy pre-built tools and the Python venv from the builder stage.
136+ COPY --from=tools /usr/local/bin/tofu /usr/local/bin/tofu
137+ COPY --from=tools /usr/local/bin/terraform /usr/local/bin/terraform
138+ COPY --from=tools /usr/local/bin/sops /usr/local/bin/sops
139+ COPY --from=tools /usr/local/bin/kubectl /usr/local/bin/kubectl
140+ COPY --from=tools /usr/local/bin/kustomize /usr/local/bin/kustomize
141+ COPY --from=tools /usr/local/bin/terraform-docs /usr/local/bin/terraform-docs
142+ COPY --from=tools /usr/local/bin/tfupdate /usr/local/bin/tfupdate
143+ COPY --from=tools /usr/local/bin/hcledit /usr/local/bin/hcledit
144+ COPY --from=tools /usr/local/bin/tflint /usr/local/bin/tflint
145+ COPY --from=tools /usr/local/bin/infracost /usr/local/bin/infracost
146+ COPY --from=tools /opt/venv /opt/venv
147+
148+ ENV PATH="/opt/venv/bin:${PATH}"
149+
150+ # git safe.directory for mounted _work volumes — system-wide covers all users.
151+ RUN git config --system --add safe.directory '*'
152+
153+ # Pre-cache pre-commit hook environments under the runner user so the cache
154+ # is owned correctly at runtime.
155+ COPY --chown=runner:runner pre-commit-config.yaml /home/runner/.pre-commit-default-config.yaml
156+
157+ USER runner
158+
159+ RUN cd /home/runner \
160+ && git init pre-commit-cache \
161+ && cd pre-commit-cache \
162+ && git config user.email "builder@localhost" \
163+ && git config user.name "Builder" \
164+ && cp /home/runner/.pre-commit-default-config.yaml .pre-commit-config.yaml \
165+ && pre-commit install-hooks \
166+ && cd /home/runner \
167+ && rm -rf pre-commit-cache
0 commit comments