File size: 3,304 Bytes
dc2b30a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c17020c
 
 
dc2b30a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5d026a6
 
 
dc2b30a
 
5d026a6
 
dc2b30a
5d026a6
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
# ──────────────────────────────────────────────────────────────────────────────
# Exocore Docs — production Docker image
#
# Two-stage build:
#   1. Node 20 builds the Vite + React SPA into ./dist
#   2. nginx-alpine serves the static bundle (no Node runtime in the
#      final image — keeps it tiny: ~25 MB compressed)
#
# Build:
#   docker build -t exocore-docs:latest -f exocore-docs/Dockerfile exocore-docs
#
# Run (host port 8080 → container port 80):
#   docker run --rm -p 8080:80 exocore-docs:latest
#
# Hugging Face Spaces: this image works as-is on a "Docker" Space — HF
# expects something on port 7860, so override at run-time:
#   docker run -p 7860:7860 -e PORT=7860 exocore-docs:latest
# ──────────────────────────────────────────────────────────────────────────────

# ───── Stage 1: build the SPA ─────
FROM node:20-bookworm-slim AS builder

WORKDIR /build

# Cache npm install on the lockfile.
COPY package*.json ./
RUN npm install --legacy-peer-deps

# Pull in the rest of the project.
COPY tsconfig.json vite.config.ts index.html ./
COPY public ./public
COPY docs   ./docs
COPY src    ./src

RUN npm run build


# ───── Stage 2: static serve via nginx ─────
FROM nginx:1.27-alpine AS runtime

# Drop the default nginx site and replace it with one that listens on
# $PORT (defaults to 80) and serves the SPA with hash-routing fallback.
RUN rm /etc/nginx/conf.d/default.conf

# Create the templates directory before writing to it
RUN mkdir -p /etc/nginx/templates

# Inline nginx config — kept here so the image is reproducible without
# any extra files outside the Docker build context.
RUN printf '%s\n' \
    'server {' \
    '    listen       ${NGINX_PORT};' \
    '    server_name  _;' \
    '    root         /usr/share/nginx/html;' \
    '    index        index.html;' \
    '' \
    '    # Long cache on hashed assets, short cache on the entry HTML.' \
    '    location /assets/ {' \
    '        add_header Cache-Control "public, max-age=31536000, immutable";' \
    '        try_files $uri =404;' \
    '    }' \
    '    location /screenshots/ {' \
    '        add_header Cache-Control "public, max-age=86400";' \
    '        try_files $uri =404;' \
    '    }' \
    '' \
    '    # Hash-router SPA fallback — every unknown path returns index.html.' \
    '    location / {' \
    '        add_header Cache-Control "no-cache";' \
    '        try_files $uri $uri/ /index.html;' \
    '    }' \
    '' \
    '    gzip            on;' \
    '    gzip_types      text/plain text/css application/javascript application/json image/svg+xml;' \
    '    gzip_min_length 1024;' \
    '}' \
    > /etc/nginx/templates/default.conf.template

COPY --from=builder /build/dist /usr/share/nginx/html

# Default to Hugging Face's required port
ENV PORT=7860
ENV NGINX_PORT=7860


# Expose the exact port Hugging Face looks for
EXPOSE 7860

CMD ["sh", "-c", "export NGINX_PORT=${PORT:-7860}; exec nginx -g 'daemon off;'"]