-
Notifications
You must be signed in to change notification settings - Fork 12
Expand file tree
/
Copy pathDockerfile
More file actions
154 lines (126 loc) · 5.57 KB
/
Copy pathDockerfile
File metadata and controls
154 lines (126 loc) · 5.57 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
# ============================================================
# NowenReader - Multi-stage Docker Build (Multi-platform)
# Builds the complete application: frontend SPA + Go backend
# Supports: linux/amd64, linux/arm64
# Final image: ~30MB (Alpine + static binary + frontend assets)
# ============================================================
# --- Stage 1: Build frontend (optional, skip if no frontend/) ---
FROM --platform=$BUILDPLATFORM node:20-alpine AS frontend-builder
WORKDIR /frontend
# Copy frontend source if it exists
# If your frontend is in a separate repo, either:
# 1. Build it externally and copy to web/dist/ before docker build
# 2. Include it as a submodule at frontend/
COPY frontend/package*.json ./
# Use China npm mirror for faster & more reliable access
RUN npm config set registry https://registry.npmmirror.com && \
npm ci --production=false
COPY frontend/ ./
RUN npm run build && \
echo "[frontend] Build succeeded" && \
ls -la /frontend/dist/ && \
test -f /frontend/dist/index.html && echo "[frontend] index.html found ✅" || \
(echo "[frontend] ERROR: index.html not found in dist/" && exit 1)
# --- Stage 2: Build Go backend ---
FROM --platform=$BUILDPLATFORM golang:1.23-alpine AS builder
# Multi-platform build args (automatically set by Docker buildx)
ARG TARGETPLATFORM
ARG TARGETOS=linux
ARG TARGETARCH
# Use Aliyun mirror for faster & more reliable access in China
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \
apk add --no-cache git
WORKDIR /build
# Use China Go module proxy for faster & more reliable access
ENV GOPROXY=https://goproxy.cn,direct
# Copy source
COPY . .
# Resolve dependencies (tidy ensures go.sum is correct)
RUN go mod tidy && go mod download
# Copy frontend build output into web/dist/ for embedding
COPY --from=frontend-builder /frontend/dist/ ./web/dist/
# Ensure dist/ has at least one file so go:embed doesn't fail on empty dir
RUN if [ -z "$(ls -A ./web/dist/ 2>/dev/null)" ]; then echo "no frontend" > ./web/dist/.gitkeep; fi
# Build static binary with version info
# Uses TARGETOS/TARGETARCH for cross-compilation (supports amd64 + arm64)
ARG VERSION=docker
ARG BUILD_TIME
ARG GIT_COMMIT
RUN BUILD_TIME=${BUILD_TIME:-$(date -u +%Y-%m-%dT%H:%M:%SZ)} && \
GIT_COMMIT=${GIT_COMMIT:-$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")} && \
echo "[build] Target platform: ${TARGETOS}/${TARGETARCH}" && \
CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build \
-ldflags="-s -w \
-X main.Version=${VERSION} \
-X main.BuildTime=${BUILD_TIME} \
-X main.GitCommit=${GIT_COMMIT}" \
-o nowen-reader ./cmd/server
# ============================================================
# Stage 3: Runtime (minimal image)
# ============================================================
FROM --platform=$TARGETPLATFORM alpine:3.20
LABEL maintainer="NowenReader"
LABEL description="NowenReader - Self-hosted comic management platform"
# Install runtime deps:
# - p7zip: for .7z/.cb7 archive extraction (also RAR fallback)
# - poppler-utils: preferred PDF page rendering (pdftoppm) and metadata (pdfinfo)
# - mupdf-tools: PDF rendering fallback (mutool draw)
# - libwebp-tools: for thumbnail WebP conversion (cwebp)
# - ffmpeg: fallback decoder for AVIF and uncommon comic page formats
# - tini: proper PID 1 signal handling
# - ca-certificates: for HTTPS requests (metadata scrapers, AI APIs)
# - tzdata: timezone support
# Note: calibre (ebook-convert) is NOT available in Alpine repos.
# MOBI/AZW3 support is optional; mount ebook-convert binary or install via pip if needed.
# Use Aliyun mirror for faster & more reliable access in China
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \
apk add --no-cache \
p7zip \
mupdf-tools \
poppler-utils \
libwebp-tools \
ffmpeg \
tini \
su-exec \
ca-certificates \
tzdata
# Create non-root user
RUN addgroup -g 1001 -S appgroup && \
adduser -u 1001 -S appuser -G appgroup
WORKDIR /app
# Copy binary from builder
COPY --from=builder /build/nowen-reader .
# Copy entrypoint script (sed removes Windows CRLF line endings if present)
COPY --chown=appuser:appgroup docker-entrypoint.sh /docker-entrypoint.sh
RUN sed -i 's/\r$//' /docker-entrypoint.sh && chmod 755 /docker-entrypoint.sh
# Create data directories with correct permissions
RUN mkdir -p /data /app/comics /app/novels /app/.cache/thumbnails /app/.cache/pages && \
chown -R appuser:appgroup /data /app /app/comics /app/novels /app/.cache
# Environment defaults
ENV GIN_MODE=release \
PORT=3000 \
DATABASE_URL=/data/nowen-reader.db \
COMICS_DIR=/app/comics \
NOVELS_DIR=/app/novels \
DATA_DIR=/app/.cache \
PUID=1001 \
PGID=1001 \
TZ=Asia/Shanghai
EXPOSE 3000
# Declare volumes for persistent data
VOLUME ["/data", "/app/comics", "/app/novels", "/app/.cache"]
# Note: NOT using USER appuser here.
# entrypoint runs as root to fix bind-mount permissions,
# then drops to appuser via su-exec before starting the server.
# Health check. BusyBox wget may not honor no_proxy consistently, so bypass
# proxies explicitly for this container-local request.
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD base_path="${BASE_PATH:-/}"; \
case "$base_path" in \
/) base_path="" ;; \
/*) base_path="${base_path%/}" ;; \
*) base_path="/${base_path%/}" ;; \
esac; \
wget -q --spider -Y off "http://localhost:${PORT:-3000}${base_path}/api/health" || exit 1
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["/docker-entrypoint.sh"]