tina4 3.8.69

Tina4 — Unified CLI for Python, PHP, Ruby, and Node.js frameworks
# Tina4 Node.js — production Docker image. Generated by `tina4 deploy docker`.
# Slim two-stage: install deps + tsx, copy into runtime.
#
# node:24 (current LTS), NOT node:20. tina4-nodejs declares engines.node >=22
# and imports the built-in `node:sqlite`, which does not exist before 22.5.
# npm downgrades an engines mismatch to a warning, so a node:20 image BUILT
# CLEANLY and then died at start with
#   ERR_UNKNOWN_BUILTIN_MODULE: No such built-in module: node:sqlite
# 24 over 22 because node:sqlite is still flagged experimental on 22 and prints
# a warning into every production log.
# The tina4 CLI is installed in EVERY Tina4 image and is the launcher: one
# `tina4 serve --production` across all four languages instead of four
# different per-language entry points. It is a single static musl binary
# copied from a published image -- a layer copy, no toolchain, no compile,
# no network fetch at build time. Override with
# --build-arg TINA4_CLI_IMAGE=... to pin or test a different CLI.
ARG TINA4_CLI_IMAGE=ghcr.io/tina4stack/tina4-cli:v3.8.63
FROM ${TINA4_CLI_IMAGE} AS tina4cli

FROM node:24-alpine AS deps
WORKDIR /app
# Full install (dev deps included) so typescript is present to COMPILE with,
# then the app is built to dist/ and the dev deps are pruned away. The runtime
# image gets compiled JS and production deps only.
#
# Why this matters: without a build step the production container ran
# `npx tsx app.ts`, i.e. a TypeScript transpiler, in production. Measured, that
# was FIVE processes (npm exec -> tsx -> node -> esbuild service) at ~455 MB
# RSS, against one process for PHP, Python and Ruby. It set a 256m memory floor
# where the other three survive on 64m, and it was the slowest of the four.
#
# Worse than slow: tsx and esbuild were NOT in the image at all. `npm ci
# --omit=dev` strips them, so npx FETCHED THE TRANSPILER OVER THE NETWORK at
# container start (observed running out of /root/.npm/_npx/...). An air-gapped
# host or a registry outage means the container never starts.
COPY package.json package-lock.json* tsconfig.json ./
RUN npm ci
COPY . .
# --noCheck: EMIT, do not type-check. An image build's job is to produce
# runnable JS, not to re-validate your dependencies' types. Your own code is
# still type-checked by `npm run build` in development, where that belongs.
#
# It is also load-bearing for now. tina4-nodejs's exports map pointed "types" at
# packages/*/src/index.ts (raw TypeScript) while "import" pointed at dist, so tsc
# dragged the FRAMEWORK's source into your program and failed on types the
# framework needs but never declared (@types/pg, a newer @types/node for
# node:sqlite). That is fixed in the framework (real .d.ts under types/, all five
# exports entries repointed) but the fix is NOT on npm yet -- the newest published
# tina4-nodejs is 3.13.92, which still carries the broken map.
#
# Drop --noCheck once the scaffold installs a tina4-nodejs NEWER than 3.13.92.
# Verify before you do: `npx tsc --noEmit` in a fresh scaffold must exit 0.
RUN npx tsc --noCheck && npm prune --omit=dev

FROM node:24-alpine
WORKDIR /app
COPY --from=tina4cli /usr/local/bin/tina4 /usr/local/bin/tina4
ENV NODE_ENV=production \
    TINA4_OVERRIDE_CLIENT=true \
    TINA4_DEBUG=false
COPY --from=deps /app/node_modules /app/node_modules
COPY --from=deps /app/dist /app/dist
COPY . /app
EXPOSE 7148

# One launcher for all four languages. --host defaults to 0.0.0.0, which is
# required: a server bound to 127.0.0.1 inside a container is unreachable
# through `docker run -p`. --no-browser because there is no browser here.
CMD ["tina4", "serve", "--production", "--no-browser"]