-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathDockerfile
More file actions
189 lines (160 loc) · 7.09 KB
/
Copy pathDockerfile
File metadata and controls
189 lines (160 loc) · 7.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# ==============================================
# Multi-stage build for cloud-agnostic deployment
# Works on: AWS Lambda, AWS Fargate, GCP Cloud Run, Azure Container Apps
# Supports: ARM64 (default) and AMD64 architectures
# ==============================================
# Build arguments for multi-architecture support
# TARGETARCH and TARGETOS are set automatically by docker buildx
ARG TARGETARCH
ARG TARGETOS=linux
# Build stage
# Image pinned to a SHA256 digest for reproducible builds — a registry
# tag mutation (Docker Hub allows re-tagging) cannot poison this build.
# To refresh: `docker buildx imagetools inspect golang:1.25.4-alpine3.21`
# (or use the Docker Hub API tags endpoint) and update the digest below.
# A Renovate / Dependabot config can automate this if desired.
FROM golang:1.25.4-alpine3.21@sha256:3289aac2aac769e031d644313d094dbda745f28af81cd7a94137e73eefd58b33 AS builder
# Re-declare args for use in this stage
ARG TARGETARCH
ARG TARGETOS
# Build metadata stamped into the binary via ldflags and surfaced by the
# public GET /version endpoint. GIT_COMMIT and BUILD_DATE are supplied by the
# terraform build module (modules/build); they default to "unknown" so a bare
# `docker build .` still succeeds without git context.
ARG VERSION=dev
ARG GIT_COMMIT=unknown
ARG BUILD_DATE
# Install build dependencies
RUN apk add --no-cache \
git \
ca-certificates \
postgresql-client \
curl
# Set shell with pipefail for safer pipe operations
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
# Install golang-migrate for database migrations (architecture-aware, checksum-verified)
RUN MIGRATE_ARCH=$([ "$TARGETARCH" = "arm64" ] && echo "arm64" || echo "amd64") && \
if [ "$MIGRATE_ARCH" = "arm64" ]; then \
MIGRATE_SHA256="9c95441cc430ffdac89276d14de5e2f18bfafca00796c2895490d62e3776d104"; \
else \
MIGRATE_SHA256="26c53c9162c9c4aaa84c47cd12455d4a9ac725befbe82850a5937b5ec1e7b8e6"; \
fi && \
curl -Lo migrate.tar.gz "https://github.com/golang-migrate/migrate/releases/download/v4.17.0/migrate.linux-${MIGRATE_ARCH}.tar.gz" && \
echo "${MIGRATE_SHA256} migrate.tar.gz" | sha256sum -c - && \
tar xzf migrate.tar.gz && \
mv migrate /usr/local/bin/migrate && \
chmod +x /usr/local/bin/migrate && \
rm migrate.tar.gz
WORKDIR /app
# Copy go module files
COPY go.mod go.sum ./
# Copy provider modules (multi-module setup)
COPY pkg/go.mod pkg/go.sum ./pkg/
COPY providers/aws/go.mod providers/aws/go.sum providers/aws/
COPY providers/azure/go.mod providers/azure/go.sum providers/azure/
COPY providers/gcp/go.mod providers/gcp/go.sum providers/gcp/
# Download dependencies
RUN go mod download
# Copy source code
COPY . .
# Build unified server binary (cloud-agnostic)
# Supports both ARM64 and AMD64 via build args
# Default: ARM64 for cost optimization (20% savings on AWS Fargate)
RUN echo "Building for ${TARGETOS}/${TARGETARCH}" && \
BUILD_TIME="${BUILD_DATE:-$(date -u +%Y-%m-%dT%H:%M:%SZ)}" && \
CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build \
-p 6 \
-ldflags="-s -w -X main.Version=${VERSION:-dev} -X main.BuildTime=${BUILD_TIME} -X main.GitSHA=${GIT_COMMIT:-unknown}" \
-o /app/cudly \
./cmd/server
# Binary built successfully
# ==============================================
# Frontend build stage
# ==============================================
# Image pinned to a SHA256 digest for reproducible builds. Refresh via
# the Docker Hub API tags endpoint and update the digest below when the
# `node:24-alpine` tag is bumped.
FROM --platform=$BUILDPLATFORM node:24-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f AS frontend-builder
WORKDIR /frontend
COPY frontend/package*.json ./
# `--no-progress`: disables the npm progress reporter, whose worker has a
# long-standing race condition in npm 10/11 that surfaces as
# `npm error Exit handler never called!` on memory-constrained hosts
# (Linux VMs, low-RAM CI runners). See npm/cli issues for the bug; the
# fix is "don't run that worker".
# `--maxsockets 1`: serialise registry fetches so peak memory during the
# install stays low. With 810 lockfile entries, parallel fetch + the
# gzip-decode workers race the OOM killer on hosts with <2 GB free.
# `--no-audit --no-fund`: skip post-install network calls that aren't
# relevant to a build context.
# `test -x`: existing guard against silent zero-exit npm failures
# (kept from #044dc583c — addresses a different failure mode where
# npm exits 0 but leaves node_modules empty).
RUN npm ci --no-progress --maxsockets 1 --no-audit --no-fund && \
test -x node_modules/.bin/webpack
COPY frontend/ ./
RUN npm run build
# ==============================================
# Runtime stage - multi-arch base image
# ==============================================
# Image pinned to a SHA256 digest for reproducible builds.
FROM alpine:3.21.3@sha256:a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c
# Re-declare args for use in this stage
ARG TARGETARCH
ARG TARGETOS
# Install runtime dependencies
RUN apk add --no-cache \
ca-certificates \
postgresql-client \
curl \
tzdata
# Create non-root user for security
RUN addgroup -g 1000 cudly && \
adduser -D -u 1000 -G cudly cudly
# Create app directory
WORKDIR /app
# Copy binary, migrations, and frontend from build stages
COPY --from=builder --chown=cudly:cudly /app/cudly /app/cudly
COPY --from=builder --chown=cudly:cudly /usr/local/bin/migrate /usr/local/bin/migrate
COPY --chown=cudly:cudly internal/database/postgres/migrations /app/migrations
COPY --from=frontend-builder --chown=cudly:cudly /frontend/dist /app/static
# Copy unified entrypoint script and set permissions
COPY --chown=cudly:cudly scripts/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
# Switch to non-root user
USER cudly
# Environment defaults
ENV DB_MIGRATIONS_PATH=/app/migrations \
DB_AUTO_MIGRATE=true \
RUNTIME_MODE=auto \
PORT=8080 \
STATIC_DIR=/app/static \
GOARCH=${TARGETARCH} \
GOOS=${TARGETOS}
# Expose HTTP port (used by Fargate, Cloud Run, Container Apps)
# Lambda ignores this
EXPOSE 8080
# Health check (works for HTTP mode, ignored in Lambda mode)
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
# Unified entrypoint handles both Lambda and HTTP modes
ENTRYPOINT ["/entrypoint.sh"]
CMD ["/app/cudly"]
# ==============================================
# Build Instructions:
# ==============================================
#
# Build for ARM64 (AWS Lambda/Fargate with Graviton):
# docker buildx build --platform linux/arm64 -t cudly:arm64 .
#
# Build for AMD64 (GCP Cloud Run, Azure Container Apps):
# docker buildx build --platform linux/amd64 -t cudly:amd64 .
#
# CI/CD builds (GitHub Actions):
# AWS Lambda/Fargate: --platform linux/arm64 (Graviton2, 20% cost savings)
# GCP Cloud Run: --platform linux/amd64 (ARM64 not supported)
# Azure Container Apps: --platform linux/amd64 (ARM64 not supported)
#
# Build and load for local testing:
# docker buildx build --platform linux/arm64 -t cudly:arm64 --load .
# ==============================================