-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathDockerfile
More file actions
202 lines (175 loc) · 8.1 KB
/
Copy pathDockerfile
File metadata and controls
202 lines (175 loc) · 8.1 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
190
191
192
193
194
195
196
197
198
199
200
201
202
# mkcert, built from source rather than taken from upstream's pre-built binary.
#
# The upstream binary (v1.4.4, published 2022-04-26) is compiled with Go 1.18,
# and Trivy's CRITICAL gate flagged four Go stdlib vulnerabilities baked into it:
# CVE-2023-24538, CVE-2023-24540, CVE-2024-24790 and CVE-2025-68121. That gate
# blocked every docker-publish run, which is why Docker Hub sat at 1.0.13 while
# the CLI reached 1.3.6.
#
# There is no newer mkcert release to bump to — v1.4.4 IS the latest and has been
# since 2022. So compile the same source with a current Go toolchain: identical
# mkcert behaviour, patched stdlib.
#
# --platform=$BUILDPLATFORM + GOOS/GOARCH cross-compilation keeps this stage
# native on the builder instead of emulated per-arch under QEMU. `go build -o` is
# used rather than `go install` because when cross-compiling `go install` writes
# to /go/bin/${GOOS}_${GOARCH}/ rather than /go/bin, and the COPY would silently
# miss it.
FROM --platform=$BUILDPLATFORM golang:1.27-alpine AS mkcert-builder
ARG TARGETOS
ARG TARGETARCH
RUN apk add --no-cache git
RUN git clone --depth 1 --branch v1.4.4 https://github.com/FiloSottile/mkcert /src
WORKDIR /src
RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
go build -trimpath -ldflags "-s -w" -o /out/mkcert .
# Multi-stage production Dockerfile for nself-admin
# Optimized for minimal size with standalone Next.js build
# Multi-platform support: linux/amd64, linux/arm64
# Build: docker build -t nself/nself-admin:v1.0.11 -t nself/nself-admin:latest .
# Stage 1: Dependencies
FROM node:22-alpine AS deps
RUN apk add --no-cache libc6-compat
RUN corepack enable && corepack prepare pnpm@10.28.0 --activate
WORKDIR /app
# Copy package files first for better layer caching
COPY package.json pnpm-lock.yaml ./
# Copy vendored local workspace packages (referenced in pnpm-lock.yaml as type:directory)
COPY packages ./packages
# Disable hardlinks: pnpm defaults to hardlink import which fails under QEMU (arm64
# emulation) because the cache mount and the workdir are on different virtual
# filesystems — pnpm exits 254. Copy mode works on all platforms.
RUN pnpm config set package-import-method copy
# Install all dependencies (needed for build)
RUN --mount=type=cache,target=/root/.local/share/pnpm/store \
pnpm install --frozen-lockfile
# Stage 2: Builder
FROM node:22-alpine AS builder
RUN apk add --no-cache libc6-compat
RUN corepack enable && corepack prepare pnpm@10.28.0 --activate
WORKDIR /app
# Copy dependencies from deps stage
COPY --from=deps /app/node_modules ./node_modules
COPY package.json pnpm-lock.yaml ./
# Copy vendored local workspace packages (required for pnpm prune --prod to resolve linked deps)
COPY packages ./packages
# Copy only necessary source files (improves cache invalidation)
COPY src ./src
COPY public ./public
COPY messages ./messages
COPY next.config.mjs ./
COPY tsconfig.json ./
COPY postcss.config.js ./
# Set build-time environment variables for standalone mode
ENV NEXT_TELEMETRY_DISABLED=1
ENV NODE_ENV=production
ENV STANDALONE=true
ENV NODE_OPTIONS=--max-old-space-size=4096
# Build the application in standalone mode
RUN pnpm run build
# Remove dev dependencies to reduce image size
RUN pnpm prune --prod
# Stage 3: Runner (minimal image)
FROM node:22-alpine AS runner
WORKDIR /app
# Install runtime dependencies including nself CLI requirements
# - bash: Required for nself CLI (Alpine uses ash by default)
# - docker-cli, docker-cli-compose: For nself to interact with Docker
# - curl, git: Required by nself commands
# - openssl, nss-tools: For SSL certificate management
# - mkcert: For local SSL certificates (installed separately)
RUN apk add --no-cache \
bash \
curl \
git \
docker-cli \
docker-cli-compose \
openssl \
nss-tools \
ca-certificates \
&& addgroup -g 1001 -S nodejs \
&& adduser -S nextjs -u 1001 \
# Remove npm from the RUNTIME image. The entrypoint runs `node server.js`
# and nothing here invokes npm or npx — the build stages use pnpm via
# corepack. But node:22-alpine ships npm 10.9.8, whose BUNDLED tar is
# 7.5.11, and that carries CVE-2026-59873. Trivy's CRITICAL gate correctly
# blocked the publish on it. Deleting npm removes the vulnerable code from
# the shipped image rather than suppressing the finding or chasing base-image
# tags for a package we do not run.
&& rm -rf /usr/local/lib/node_modules/npm \
/usr/local/bin/npm \
/usr/local/bin/npx
# Install mkcert for local SSL certificate generation.
# Built from source in the mkcert-builder stage at the top of this file — see
# there for why the upstream pre-built binary cannot be used.
COPY --from=mkcert-builder /out/mkcert /usr/local/bin/mkcert
RUN chmod +x /usr/local/bin/mkcert
# Install nself CLI pre-built binary
ARG NSELF_VERSION=1.3.6
RUN ARCH=$(uname -m) && \
if [ "$ARCH" = "x86_64" ]; then NSELF_ARCH="amd64"; \
elif [ "$ARCH" = "aarch64" ]; then NSELF_ARCH="arm64"; \
else NSELF_ARCH="amd64"; fi && \
mkdir -p /tmp/nself-install && \
curl -fsSL "https://github.com/nself-org/cli/releases/download/v${NSELF_VERSION}/nself-${NSELF_VERSION}-linux-${NSELF_ARCH}.tar.gz" \
| tar -xz -C /tmp/nself-install --strip-components=1 && \
mv /tmp/nself-install/nself /usr/local/bin/nself && \
chmod +x /usr/local/bin/nself && \
rm -rf /tmp/nself-install
# Note: For development, mount your local nself source at /opt/nself to override
# The symlink at /usr/local/bin/nself will work with either installed or mounted source
# Copy only the standalone build output (much smaller!)
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
# Copy graceful shutdown wrapper
COPY --chown=nextjs:nodejs scripts/docker-entrypoint.sh /app/docker-entrypoint.sh
RUN chmod +x /app/docker-entrypoint.sh
# Create necessary directories for project mount and database
RUN mkdir -p /workspace /app/data \
&& chown -R nextjs:nodejs /workspace /app/data
# Set runtime environment variables
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
ENV HOSTNAME="0.0.0.0"
# Port 3021 is the reserved port for nself-admin (not 3100, which is for Loki)
ENV PORT=3021
ENV ADMIN_VERSION=1.3.6
# Environment variables that can be set at runtime:
# NSELF_PROJECT_PATH - Path to mounted project (default: /workspace)
# NSELF_CLI_PATH - Override nself CLI location (default: /usr/local/bin/nself)
# DOCKER_HOST - Docker socket (default: unix:///var/run/docker.sock)
# Add labels for container metadata
LABEL org.opencontainers.image.title="nself-admin"
LABEL org.opencontainers.image.description="Web-based administration interface for nself CLI"
LABEL org.opencontainers.image.version="1.3.6"
LABEL org.opencontainers.image.vendor="nself.org"
LABEL org.opencontainers.image.source="https://github.com/nself-org/admin"
LABEL org.opencontainers.image.licenses="Proprietary - Free for personal use, Commercial license required"
LABEL org.opencontainers.image.documentation="https://github.com/nself-org/admin/wiki"
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3021/api/health || exit 1
# SECURITY NOTE: Running as root to access Docker socket for nself commands
# This is required because the Docker socket is typically owned by root:docker
#
# For better security in production, consider one of these alternatives:
#
# Option A: Docker Socket Proxy (most secure)
# - Use tecnativa/docker-socket-proxy
# - Set DOCKER_HOST=tcp://docker-socket-proxy:2375
# - Remove socket mount from nself-admin
#
# Option B: Match Host Docker GID
# - Pass DOCKER_GID build arg matching host's docker group
# - Uncomment USER nextjs below
#
# For local development, running as root is acceptable.
# USER nextjs
# Expose port 3021 (reserved for nself-admin, distinct from Loki on 3100)
EXPOSE 3021
# Use STOPSIGNAL for proper graceful shutdown
STOPSIGNAL SIGTERM
# Start the application using the entrypoint wrapper for graceful shutdown
ENTRYPOINT ["/app/docker-entrypoint.sh"]
CMD ["node", "server.js"]