|
1 | 1 | # Universal Dockerfile for DevOps Daily |
2 | | -# Switch environments with: BUILD_ENV=development or BUILD_ENV=production |
| 2 | +# Multi-stage build for optimized development and production images |
3 | 3 |
|
4 | | -ARG NODE_VERSION=20.18.1 |
5 | | -ARG PNPM_VERSION=10.11.1 |
6 | | -ARG BUILD_ENV=production |
| 4 | +ARG NODE_VERSION=22.13.1 |
| 5 | +ARG PNPM_VERSION=10.28.1 |
7 | 6 |
|
8 | | -FROM node:${NODE_VERSION}-bullseye-slim |
| 7 | +# =========================================================================== |
| 8 | +# Stage 1: Base image with common dependencies |
| 9 | +# =========================================================================== |
| 10 | +FROM node:${NODE_VERSION}-bookworm-slim AS base |
9 | 11 |
|
10 | | -ARG BUILD_ENV |
11 | 12 | ARG PNPM_VERSION |
12 | 13 |
|
13 | | -# Install system packages based on environment |
14 | 14 | RUN apt-get update && \ |
15 | 15 | apt-get upgrade -y && \ |
16 | | - if [ "$BUILD_ENV" = "production" ]; then \ |
17 | | - apt-get install -y --no-install-recommends nginx curl; \ |
18 | | - else \ |
19 | | - apt-get install -y --no-install-recommends curl; \ |
20 | | - fi && \ |
| 16 | + apt-get install -y --no-install-recommends curl && \ |
21 | 17 | apt-get clean && \ |
22 | 18 | rm -rf /var/lib/apt/lists/* |
23 | 19 |
|
24 | | -# Install pnpm |
25 | 20 | RUN corepack enable && \ |
26 | 21 | corepack prepare pnpm@${PNPM_VERSION} --activate |
27 | 22 |
|
28 | 23 | WORKDIR /app |
29 | 24 |
|
30 | | -# Copy package files |
31 | | -COPY package.json pnpm-lock.yaml ./ |
| 25 | +# =========================================================================== |
| 26 | +# Stage 2: Development image |
| 27 | +# Hot-reload enabled for local development |
| 28 | +# =========================================================================== |
| 29 | +FROM base AS development |
32 | 30 |
|
33 | | -# Install dependencies based on environment |
34 | | -RUN if [ "$BUILD_ENV" = "production" ]; then \ |
35 | | - pnpm install --frozen-lockfile --prod; \ |
36 | | - else \ |
37 | | - pnpm install --frozen-lockfile; \ |
38 | | - fi |
| 31 | +COPY package.json pnpm-lock.yaml ./ |
| 32 | +RUN pnpm install --frozen-lockfile |
39 | 33 |
|
40 | | -# Copy source code |
41 | 34 | COPY . . |
42 | 35 |
|
43 | | -# Build for production if needed |
44 | | -RUN if [ "$BUILD_ENV" = "production" ]; then \ |
45 | | - NODE_ENV=production NEXT_TELEMETRY_DISABLED=1 pnpm run build:cf && \ |
46 | | - rm -rf /var/www/html/* && \ |
47 | | - mkdir -p /var/www/html && \ |
48 | | - cp -r /app/out/* /var/www/html/ && \ |
49 | | - echo 'server {' > /etc/nginx/sites-available/default && \ |
50 | | - echo ' listen 80;' >> /etc/nginx/sites-available/default && \ |
51 | | - echo ' listen [::]:80;' >> /etc/nginx/sites-available/default && \ |
52 | | - echo ' server_name localhost;' >> /etc/nginx/sites-available/default && \ |
53 | | - echo ' root /var/www/html;' >> /etc/nginx/sites-available/default && \ |
54 | | - echo ' index index.html;' >> /etc/nginx/sites-available/default && \ |
55 | | - echo '' >> /etc/nginx/sites-available/default && \ |
56 | | - echo ' gzip on;' >> /etc/nginx/sites-available/default && \ |
57 | | - echo ' gzip_vary on;' >> /etc/nginx/sites-available/default && \ |
58 | | - echo ' gzip_min_length 1024;' >> /etc/nginx/sites-available/default && \ |
59 | | - echo ' gzip_types text/plain text/css text/xml text/javascript application/javascript application/json;' >> /etc/nginx/sites-available/default && \ |
60 | | - echo '' >> /etc/nginx/sites-available/default && \ |
61 | | - echo ' add_header X-Frame-Options "SAMEORIGIN" always;' >> /etc/nginx/sites-available/default && \ |
62 | | - echo ' add_header X-Content-Type-Options "nosniff" always;' >> /etc/nginx/sites-available/default && \ |
63 | | - echo ' add_header X-XSS-Protection "1; mode=block" always;' >> /etc/nginx/sites-available/default && \ |
64 | | - echo ' add_header Referrer-Policy "strict-origin-when-cross-origin" always;' >> /etc/nginx/sites-available/default && \ |
65 | | - echo ' add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;' >> /etc/nginx/sites-available/default && \ |
66 | | - echo '' >> /etc/nginx/sites-available/default && \ |
67 | | - echo ' location ~* \\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {' >> /etc/nginx/sites-available/default && \ |
68 | | - echo ' expires 1y;' >> /etc/nginx/sites-available/default && \ |
69 | | - echo ' add_header Cache-Control "public, immutable";' >> /etc/nginx/sites-available/default && \ |
70 | | - echo ' }' >> /etc/nginx/sites-available/default && \ |
71 | | - echo '' >> /etc/nginx/sites-available/default && \ |
72 | | - echo ' location / {' >> /etc/nginx/sites-available/default && \ |
73 | | - echo ' try_files \$uri \$uri.html /index.html;' >> /etc/nginx/sites-available/default && \ |
74 | | - echo ' }' >> /etc/nginx/sites-available/default && \ |
75 | | - echo '' >> /etc/nginx/sites-available/default && \ |
76 | | - echo ' error_page 404 /404.html;' >> /etc/nginx/sites-available/default && \ |
77 | | - echo '}' >> /etc/nginx/sites-available/default; \ |
78 | | - fi |
79 | | - |
80 | | -# Environment variables |
81 | | -ENV NODE_ENV=${BUILD_ENV:-production} |
| 36 | +ENV NODE_ENV=development |
82 | 37 | ENV NEXT_TELEMETRY_DISABLED=1 |
83 | 38 | ENV WATCHPACK_POLLING=true |
84 | 39 |
|
85 | | -# Metadata labels |
86 | 40 | LABEL org.opencontainers.image.title="DevOps Daily" |
87 | 41 | LABEL org.opencontainers.image.description="A modern content platform for DevOps professionals" |
88 | 42 | LABEL org.opencontainers.image.source="https://github.com/The-DevOps-Daily/devops-daily" |
89 | 43 | LABEL org.opencontainers.image.licenses="MIT" |
90 | 44 | LABEL org.opencontainers.image.vendor="DevOps Daily" |
91 | 45 | LABEL org.opencontainers.image.authors="DevOps Daily Team" |
92 | 46 |
|
93 | | -# Expose ports (both for flexibility) |
94 | | -EXPOSE 3000 80 |
| 47 | +EXPOSE 3000 |
95 | 48 |
|
96 | | -# Health check that adapts to environment |
97 | 49 | HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ |
98 | | - CMD if [ "$NODE_ENV" = "production" ]; then \ |
99 | | - curl -f http://localhost:80/ || exit 1; \ |
100 | | - else \ |
101 | | - curl -f http://localhost:3000/ || exit 1; \ |
102 | | - fi |
103 | | - |
104 | | -# Start command based on environment |
105 | | -CMD ["/bin/sh", "-c", "if [ \"$NODE_ENV\" = \"production\" ]; then nginx -g 'daemon off;'; else pnpm run dev; fi"] |
| 50 | + CMD curl -f http://localhost:3000/ || exit 1 |
| 51 | + |
| 52 | +CMD ["pnpm", "run", "dev"] |
| 53 | + |
| 54 | +# =========================================================================== |
| 55 | +# Stage 3: Production builder |
| 56 | +# Builds the static site for production |
| 57 | +# =========================================================================== |
| 58 | +FROM base AS builder |
| 59 | + |
| 60 | +COPY package.json pnpm-lock.yaml ./ |
| 61 | +RUN pnpm install --frozen-lockfile |
| 62 | + |
| 63 | +COPY . . |
| 64 | + |
| 65 | +ENV NODE_ENV=production |
| 66 | +ENV NEXT_TELEMETRY_DISABLED=1 |
| 67 | + |
| 68 | +RUN pnpm run build:cf |
| 69 | + |
| 70 | +# =========================================================================== |
| 71 | +# Stage 4: Production runtime |
| 72 | +# Minimal nginx image serving static files |
| 73 | +# =========================================================================== |
| 74 | +FROM nginx:1.27-alpine AS production |
| 75 | + |
| 76 | +RUN rm /etc/nginx/conf.d/default.conf |
| 77 | + |
| 78 | +COPY docker/nginx.conf /etc/nginx/nginx.conf |
| 79 | +COPY --from=builder /app/out /usr/share/nginx/html |
| 80 | + |
| 81 | +RUN chown -R nginx:nginx /usr/share/nginx/html && \ |
| 82 | + chown -R nginx:nginx /var/cache/nginx && \ |
| 83 | + chown -R nginx:nginx /var/log/nginx && \ |
| 84 | + touch /var/run/nginx.pid && \ |
| 85 | + chown -R nginx:nginx /var/run/nginx.pid |
| 86 | + |
| 87 | +ENV NODE_ENV=production |
| 88 | +ENV NEXT_TELEMETRY_DISABLED=1 |
| 89 | + |
| 90 | +LABEL org.opencontainers.image.title="DevOps Daily" |
| 91 | +LABEL org.opencontainers.image.description="A modern content platform for DevOps professionals" |
| 92 | +LABEL org.opencontainers.image.source="https://github.com/The-DevOps-Daily/devops-daily" |
| 93 | +LABEL org.opencontainers.image.licenses="MIT" |
| 94 | +LABEL org.opencontainers.image.vendor="DevOps Daily" |
| 95 | +LABEL org.opencontainers.image.authors="DevOps Daily Team" |
| 96 | + |
| 97 | +EXPOSE 80 |
| 98 | + |
| 99 | +HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \ |
| 100 | + CMD wget --no-verbose --tries=1 --spider http://localhost:80/ || exit 1 |
| 101 | + |
| 102 | +CMD ["nginx", "-g", "daemon off;"] |
0 commit comments