Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 73 additions & 2 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,77 @@
# Dependencies
node_modules
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Build outputs
.next
out
build
dist

# Environment files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local

# Testing
coverage
.nyc_output
.jest

# Development
.vscode
.idea
*.swp
*.swo
*~

# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

# Git
.git
.gitignore

# Docker
.dockerignore
Dockerfile*
docker-compose*

# CI/CD
.github
.git
Dockerfile
.gitlab-ci.yml

# Documentation
README.md
CHANGELOG.md
LICENSE
docs/

# Test files
**/*.test.js
**/*.test.ts
**/*.spec.js
**/*.spec.ts
**/test
**/tests
**/__tests__
e2e/

# Logs
logs
*.log

# Cache
.cache
.tmp
temp/
87 changes: 87 additions & 0 deletions DOCKER_OPTIMIZATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Docker Size Optimization Guide

This document explains the optimizations made to reduce your Docker container size from 2.3GB.

## Key Issues Identified

1. **Complete node_modules copied**: Original Dockerfile copied all dependencies including dev dependencies
2. **No dependency pruning**: Many unnecessary files (tests, docs, cache) were included
3. **Inefficient Next.js output**: Not using standalone mode which creates optimized bundles
4. **Large base image**: Using full Node.js Alpine when distroless would be smaller

## Optimization Strategies

### 1. Current Optimized Dockerfile
- **Expected size reduction**: ~60-70% (700MB-1GB)
- Uses Next.js standalone output
- Separates production dependencies
- Cleans up unnecessary files
- Better caching strategy

### 2. Distroless Version (Dockerfile.optimized)
- **Expected size reduction**: ~80-85% (300-500MB)
- Uses Google's distroless base image
- Most secure option (no shell, minimal attack surface)
- Smallest possible size

## Build Instructions

### Option 1: Use optimized Dockerfile (recommended for most cases)
```bash
docker build -t your-app:optimized .
```

### Option 2: Use distroless version (smallest size)
```bash
docker build -f Dockerfile.optimized -t your-app:distroless .
```

## Additional Optimizations

### 1. .dockerignore improvements
Add these entries to your .dockerignore:
```
**/.git
**/node_modules
**/.next
**/coverage
**/.nyc_output
**/test
**/tests
**/__tests__
**/*.test.js
**/*.test.ts
**/*.spec.js
**/*.spec.ts
**/README.md
**/CHANGELOG.md
**/.github
**/docs
**/examples
```

### 2. Package.json optimization
Consider removing unused dependencies:
- Run `npx depcheck` to find unused packages
- Move dev-only packages to devDependencies
- Use tree-shaking friendly imports

### 3. Next.js bundle analysis
```bash
ANALYZE=true yarn build
```

## Security Benefits
- Runs as non-root user
- Minimal attack surface with distroless
- No shell access in production
- Updated base images

## Health Check Optimization
- Increased interval from 5s to 30s (reduces overhead)
- Proper signal handling with dumb-init

## Expected Results
- **Original**: ~2.3GB
- **Optimized**: ~700MB-1GB (70% reduction)
- **Distroless**: ~300-500MB (85% reduction)
55 changes: 44 additions & 11 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,25 @@
COPY .yarn .yarn
RUN yarn workspaces focus --all --production

# Create a production-only dependencies stage
FROM base AS deps-prod
COPY .yarn .yarn
RUN yarn workspaces focus --all --production && \
yarn cache clean && \
rm -rf .yarn/cache

# Build target builder #
########################
FROM base AS builder

# Setup build env
ARG VERSION=unversioned
ARG SENTRY_AUTH_TOKEN

Check warning on line 31 in Dockerfile

View workflow job for this annotation

GitHub Actions / Tests / Build and scan frontend container image

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ARG "SENTRY_AUTH_TOKEN") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/
ENV SENTRY_AUTH_TOKEN="$SENTRY_AUTH_TOKEN"

Check warning on line 32 in Dockerfile

View workflow job for this annotation

GitHub Actions / Tests / Build and scan frontend container image

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "SENTRY_AUTH_TOKEN") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/
ARG GHOST_API_URL
ENV GHOST_API_URL="$GHOST_API_URL"
ARG GHOST_CONTENT_KEY

Check warning on line 35 in Dockerfile

View workflow job for this annotation

GitHub Actions / Tests / Build and scan frontend container image

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ARG "GHOST_CONTENT_KEY") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/
ENV GHOST_CONTENT_KEY="$GHOST_CONTENT_KEY"

Check warning on line 36 in Dockerfile

View workflow job for this annotation

GitHub Actions / Tests / Build and scan frontend container image

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "GHOST_CONTENT_KEY") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/

RUN apk add --no-cache jq && \
mv package.json package.json.bak && \
Expand All @@ -34,35 +41,61 @@
rm package.json.bak && \
apk del jq

# Add dev deps
COPY --from=deps /app/node_modules ./node_modules
# Add ALL deps for building (including dev dependencies)
COPY .yarn .yarn
RUN yarn install --immutable

COPY . .

RUN yarn build && \
yarn sitemap

# Clean up build cache and unnecessary files
RUN rm -rf .yarn/cache && \
rm -rf node_modules/.cache && \
find ./node_modules -name "*.md" -delete && \
find ./node_modules -name "*.txt" -delete && \
find ./node_modules -name "test" -type d -exec rm -rf {} + || true && \
find ./node_modules -name "tests" -type d -exec rm -rf {} + || true && \
find ./node_modules -name "__tests__" -type d -exec rm -rf {} + || true

# Build target production #
###########################
FROM base AS runner
FROM node:20-alpine AS runner

RUN apk --no-cache add curl
# Install only essential packages
RUN apk --no-cache add curl && \
apk --no-cache add dumb-init

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# Create user for security
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 nextjs

COPY --from=deps --chown=nextjs:nodejs /app/node_modules ./node_modules
# Set working directory
WORKDIR /app

# Copy only production dependencies from deps-prod stage
COPY --from=deps-prod --chown=nextjs:nodejs /app/node_modules ./node_modules

# Copy built application from builder
COPY --from=builder --chown=nextjs:nodejs /app/next-i18next.config.js ./
COPY --from=builder --chown=nextjs:nodejs /app/next.config.js ./
COPY --from=builder --chown=nextjs:nodejs /app/package.json ./
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next

# Only copy the production build, not the entire .next folder
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

ENV PORT 3040
ENV PORT=3040
ENV NODE_ENV=production

EXPOSE 3040

CMD [ "npm", "run", "start" ]
# Use dumb-init for proper signal handling
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "server.js"]

HEALTHCHECK --interval=5s --timeout=3s --retries=3 CMD curl --fail http://localhost:3040 || exit 1
HEALTHCHECK --interval=30s --timeout=3s --retries=3 CMD curl --fail http://localhost:3040 || exit 1
66 changes: 66 additions & 0 deletions Dockerfile.optimized
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Build target dependencies #
###########################
FROM node:20-alpine AS base
WORKDIR /app
ARG NODE_ENV=production
ENV PATH=/app/node_modules/.bin:$PATH \
NODE_ENV="$NODE_ENV"
# Yarn
RUN yarn set version berry
COPY package.json yarn.lock* .yarnrc.yml ./

# Production dependencies only
FROM base AS deps-prod
COPY .yarn .yarn
RUN yarn workspaces focus --all --production && \
yarn cache clean && \
rm -rf .yarn/cache

# Build target builder #
########################
FROM base AS builder

# Setup build env
ARG VERSION=unversioned
ARG SENTRY_AUTH_TOKEN
ENV SENTRY_AUTH_TOKEN="$SENTRY_AUTH_TOKEN"
ARG GHOST_API_URL
ENV GHOST_API_URL="$GHOST_API_URL"
ARG GHOST_CONTENT_KEY
ENV GHOST_CONTENT_KEY="$GHOST_CONTENT_KEY"

RUN apk add --no-cache jq && \
mv package.json package.json.bak && \
jq --arg version "$VERSION" '.version=$version' package.json.bak > package.json && \
rm package.json.bak && \
apk del jq

# Add ALL deps for building (including dev dependencies)
COPY .yarn .yarn
RUN yarn install --immutable

COPY . .

# Build the application with standalone output
RUN yarn build && \
yarn sitemap

# Build target production with distroless image #
################################################
FROM gcr.io/distroless/nodejs20-debian12:latest AS runner

# Copy the standalone output which includes only necessary files
COPY --from=builder --chown=65534:65534 /app/.next/standalone /app/
COPY --from=builder --chown=65534:65534 /app/.next/static /app/.next/static
COPY --from=builder --chown=65534:65534 /app/public /app/public

WORKDIR /app

USER 65534

ENV NODE_ENV=production
ENV PORT=3040

EXPOSE 3040

CMD ["server.js"]
1 change: 1 addition & 0 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const { i18n } = require('./next-i18next.config')
const moduleExports = {
i18n,
reactStrictMode: true,
output: 'standalone',
sassOptions: {
includePaths: [path.join(__dirname, 'src/styles')],
},
Expand Down
Loading