File size: 5,889 Bytes
0984f9f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5b03d64
 
 
0984f9f
 
 
 
 
 
 
 
 
 
729138f
0984f9f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# syntax=docker/dockerfile:1
#
# Hugging Face Space (Docker SDK) for Mixly.
#
# The app source lives in the PRIVATE GitHub repo, not in this Space repo β€” this
# Dockerfile clones it at build time using the GITHUB_TOKEN *build secret*, builds
# the Next.js app, then runs it. Keeping the Space tiny means a rebuild always
# pulls the latest `main` from GitHub.
#
# The SQLite DB lives on the /data mount. On boot it is restored from the HF Storage
# Bucket (S3 API) if /data is empty, so data survives even without paid Persistent
# Storage (see scripts/restore-db.mjs).

# ---- builder ----
FROM node:24-slim AS builder
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates \
    && rm -rf /var/lib/apt/lists/*

# Which repo/branch to deploy. Override GITHUB_REF (Space variable) to pin a release.
ARG GITHUB_REPO=github.com/phamdung2209/Mixly.git
ARG GITHUB_REF=main

# Clone with the token mounted as a build secret β€” it is never written into an
# image layer. Add GITHUB_TOKEN in Space β†’ Settings β†’ Secrets.
RUN --mount=type=secret,id=GITHUB_TOKEN,required=true \
    git clone --depth 1 --branch "${GITHUB_REF}" \
      "https://x-access-token:$(cat /run/secrets/GITHUB_TOKEN)@${GITHUB_REPO}" .

ENV NEXT_TELEMETRY_DISABLED=1
# Placeholder values ONLY so env validation passes during `next build` (some server
# modules read env at import). The app reads the REAL values from HF secrets at
# runtime; none of these are NEXT_PUBLIC, so nothing is baked into the output.
ENV SHOPIFY_API_KEY=build SHOPIFY_API_SECRET=build SHOPIFY_SCOPES=read_products \
    SHOPIFY_APP_URL=https://build.invalid SHOPIFY_API_VERSION=2026-07 DATABASE_URL=file:/tmp/build.db
# NEXT_PUBLIC_* is inlined into the client bundle at BUILD time, so it must exist
# during `npm run build` β€” a runtime secret is too late. The Shopify API key is
# public (it ships in the browser), so mounting the secret just for the build is safe.
RUN --mount=type=secret,id=NEXT_PUBLIC_SHOPIFY_API_KEY \
    --mount=type=secret,id=NEXT_PUBLIC_CRISP_WEBSITE_ID \
    export NEXT_PUBLIC_SHOPIFY_API_KEY="$(cat /run/secrets/NEXT_PUBLIC_SHOPIFY_API_KEY 2>/dev/null)" \
           NEXT_PUBLIC_CRISP_WEBSITE_ID="$(cat /run/secrets/NEXT_PUBLIC_CRISP_WEBSITE_ID 2>/dev/null)" && \
    npm ci && npx prisma generate && npm run build && mkdir -p public

# Deploy the Shopify Function + app config as part of the Space build, so ONE rebuild
# ships the web app AND the extensions together. Only runs on a real rebuild (not on
# restart/wake), so no version churn. NON-FATAL: a Shopify hiccup never breaks the web
# deploy. Needs the SHOPIFY_CLI_PARTNERS_TOKEN Space secret (else it just skips).
RUN --mount=type=secret,id=SHOPIFY_CLI_PARTNERS_TOKEN \
    if [ -s /run/secrets/SHOPIFY_CLI_PARTNERS_TOKEN ]; then \
      export SHOPIFY_CLI_PARTNERS_TOKEN="$(cat /run/secrets/SHOPIFY_CLI_PARTNERS_TOKEN)" CI=true; \
      for d in extensions/*/; do [ -f "${d}package.json" ] && (cd "$d" && npm install --no-audit --no-fund || true); done; \
      npx shopify app deploy --config=production --allow-updates || echo "WARN: shopify app deploy failed (non-fatal)"; \
    else echo "SHOPIFY_CLI_PARTNERS_TOKEN not set β€” skipping Shopify deploy"; fi

# ---- runner ----
FROM node:24-slim AS runner
WORKDIR /app
ENV NODE_ENV=production NEXT_TELEMETRY_DISABLED=1 PORT=3000 HOSTNAME=0.0.0.0

# tini as PID 1: forwards SIGTERM to the app and reaps zombies. Without it (e.g.
# `npm start` as PID 1) npm swallows SIGTERM and never forwards it to the Node
# server, so the shutdown DB backup (src/lib/backup.ts) never runs on a rebuild.
RUN apt-get update && apt-get install -y --no-install-recommends tini \
    && rm -rf /var/lib/apt/lists/*

# node:24-slim already ships a `node` user at UID 1000 (which HF mounts /data for) β€”
# reuse it; creating a second UID-1000 user fails with "UID 1000 is not unique".
COPY --from=builder --chown=node /app/node_modules ./node_modules
COPY --from=builder --chown=node /app/.next ./.next
COPY --from=builder --chown=node /app/public ./public
COPY --from=builder --chown=node /app/package.json ./package.json
COPY --from=builder --chown=node /app/next.config.ts ./next.config.ts
COPY --from=builder --chown=node /app/prisma ./prisma
COPY --from=builder --chown=node /app/prisma.config.ts ./prisma.config.ts
COPY --from=builder --chown=node /app/scripts ./scripts

# Make /data writable with OR without Persistent Storage. If enabled, HF mounts
# /data (uid 1000) over this. If not, this ephemeral dir is used and data still
# survives rebuilds via the S3 bucket restore on boot (scripts/restore-db.mjs).
RUN mkdir -p /data && chown node:node /data
USER node

EXPOSE 3000
# 1) restore the DB from the HF Storage Bucket (S3) if /data is empty, 2) apply migrations,
# 3) start. Restore MUST run before migrate β€” migrate would otherwise create an
# empty DB and the restore would think data already exists.
# `exec next` (NOT `npm start`) so the Node server itself becomes the process that
# receives SIGTERM on rebuild/sleep β€” that's what triggers the final DB backup
# (src/lib/backup.ts). npm would fork the server as a child and swallow the signal.
# tini (ENTRYPOINT) forwards the signal and reaps zombies.
ENTRYPOINT ["tini", "--"]
# Prisma Studio runs in the background on port 5555. NOTE: prisma studio v7 binds
# 0.0.0.0 (no hostname flag) β€” it is NOT localhost-only. It stays private because HF
# routes ONLY port 3000 as ingress and network-isolates the container; the DB is
# reached via the operator + same-origin gated route handlers (src/lib/ops/studio-proxy)
# at /ops/studio. Do NOT reuse this where 5555 is published (e.g. docker -p 5555:5555).
CMD ["sh", "-c", "node scripts/restore-db.mjs && node_modules/.bin/prisma migrate deploy && (node_modules/.bin/prisma studio --port 5555 --browser none &) && exec node_modules/.bin/next start"]