Dockerfile
Rama main · réplica de lectura del repositorio original.
# attest_tag: console (Next.js static export) + static Go binary + Litestream.
#
# Multi-arch without QEMU. CGO_ENABLED=0 and pure-Go SQLite (modernc.org/sqlite) mean Go
# cross-compiles, so the two build stages are pinned to the BUILDER's platform and only the
# `go build` is told what to target. Building linux/arm64 on an amd64 runner therefore costs
# the same as building amd64, with no emulation anywhere.
#
# The last two stages are deliberately NOT pinned: they contribute real binaries (litestream,
# alpine's poppler) and must resolve to the TARGET architecture.
# BUILDPLATFORM carries a default so that a builder which does not set it — Cloud Build's source
# deploys run classic `docker build`, not buildx — still parses these FROM lines rather than dying
# on `failed to parse platform ""`. BuildKit overrides it, so buildx is unaffected. TARGETOS and
# TARGETARCH must NOT be declared here: a global default wins over BuildKit's own value, and an
# arm64 image would quietly get an amd64 binary. Declared per stage, they stay the builder's.
ARG BUILDPLATFORM=linux/amd64
FROM --platform=$BUILDPLATFORM node:26-alpine AS uibuild
WORKDIR /ui
COPY ui/package.json ui/package-lock.json ./
RUN npm ci --no-audit --no-fund
COPY ui/ ./
RUN npm run build
FROM --platform=$BUILDPLATFORM golang:1.27.1-alpine AS build
ARG TARGETOS
ARG TARGETARCH
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
COPY --from=uibuild /ui/out ./ui/out
# The release workflow passes the tag (v0.1.0) for `attesttag version` and the startup line, which
# print it without the v. Declared here, after the module download, so a new version reuses that
# layer; a build that passes none, a Cloud Run source deploy among them, says "dev".
ARG VERSION="dev"
RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH \
go build -trimpath -ldflags="-s -w -X attesttag/internal/app.Version=${VERSION#v}" -o /attesttag ./cmd/attesttag
FROM litestream/litestream:0.5.17 AS litestream
FROM alpine:3.24
RUN apk add --no-cache ca-certificates tzdata poppler-utils
WORKDIR /app
COPY --from=build /attesttag /app/attesttag
COPY --from=litestream /usr/local/bin/litestream /usr/local/bin/litestream
# No litestream.yml is baked in: the bot renders one at startup for whichever bucket it was
# given (internal/app/replica_target.go), because a file written at build time cannot know
# whether this deployment replicates to GCS or to an S3-compatible store.
# Nothing here needs root: the bot feeds attacker-supplied PDFs to pdftotext, so it runs as
# an ordinary user that owns only the database and docs directories.
RUN adduser -D -u 10001 attest \
&& mkdir -p /data /app/docs \
&& chown -R attest:attest /data /app/docs
USER attest
# -trimpath strips the VCS stamp out of build info, so the commit has to arrive as a build arg —
# the first thing worth knowing about a bug report. It becomes the revision label at the bottom of
# this file, and, where SQLite is replicated to a bucket, the commit the write lease records for
# the container holding the database (internal/app/lease.go). `attesttag version` and the startup
# line print it beside the version; neither !whoami nor /health says it.
ARG GIT_COMMIT=""
ENV GIT_COMMIT=$GIT_COMMIT
ENV DB_PATH=/data/attesttag.db DOCS_DIR=/app/docs PORT=8080
# Fail closed on a missing MASTER_KEY rather than minting a throwaway one that silently loses
# every stored credential on the next restart. In a container there is no Cloud Run metadata to
# infer "managed" from, so this is what makes NewSealer refuse instead of generate. It also turns
# on the matching hardening (no LLM_DUMP_DIR, cookies always Secure).
ENV REQUIRE_MASTER_KEY=1
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=5s CMD wget -qO- http://127.0.0.1:8080/health || exit 1
# The binary is PID 1 and runs Litestream as its child, not the other way round: the restore has
# to wait for the write lease, which only the bot can take. See internal/app/replication.go.
ENTRYPOINT ["/app/attesttag"]
ARG SOURCE_URL="https://github.com/Attest-Tag/attesttag"
ARG VERSION="dev"
LABEL org.opencontainers.image.title="attest_tag" \
org.opencontainers.image.description="An AI teammate for Slack that acts — and never holds a key." \
org.opencontainers.image.source=$SOURCE_URL \
org.opencontainers.image.revision=$GIT_COMMIT \
org.opencontainers.image.version=$VERSION \
org.opencontainers.image.licenses="MIT"