Typescript #
TypeScript sering dianggap “hanya Node.js dengan typing”. Akibatnya, strategi Docker untuk TypeScript sering disamakan mentah-mentah dengan JavaScript, padahal di production, TypeScript punya fase build eksplisit yang harus diperlakukan dengan benar.
Jika salah strategi, aplikasi TypeScript bisa menghasilkan image:
- 300–600 MB
- penuh
node_modules - lambat di-pull Kubernetes
Artikel ini membahas secara mendetail, rasional, dan production-oriented bagaimana membangun Docker image TypeScript yang kecil, aman, dan maintainable, sejajar dengan praktik terbaik di Java (distroless), Python, dan Go.
Realita Ukuran Docker Image TypeScript #
Tanpa Strategi #
| Setup | Ukuran Image |
|---|---|
| node:latest + ts-node | 500–700 MB |
| node:alpine + npm install | 300–450 MB |
Dengan Strategi yang Benar #
| Setup | Ukuran Image |
|---|---|
| Multi-stage + prune | 120–180 MB |
| Distroless Node.js | 60–110 MB |
| Bundling (esbuild) | 30–60 MB ⭐ |
Masalah Utama TypeScript di Docker #
1. TypeScript Tidak Dibutuhkan di Runtime #
tscts-node- source
.ts
Semua ini harus mati di runtime image.
2. node_modules Mengandung Dua Dunia
#
- devDependencies (TypeScript, lint, test)
- production dependencies
Tanpa disiplin, keduanya ikut masuk.
3. Build Tool Bocor ke Runtime #
- source map
- test file
- CLI tool
Prinsip Utama Image TypeScript Production #
Runtime image hanya menjalankan JavaScript hasil build.
Idealnya runtime hanya berisi:
- Node.js runtime
- JavaScript hasil transpile
- Production dependency (atau bahkan tanpa dependency)
Strategi 1: Multi-stage Build (Fondasi Wajib) #
Dockerfile Dasar (Belum Optimal) #
# =====================
# Build stage
# =====================
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json tsconfig.json ./
RUN npm ci
COPY src ./src
RUN npm run build # tsc -> dist/
# =====================
# Runtime stage
# =====================
FROM node:20-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package.json .
EXPOSE 3000
CMD ["node", "dist/index.js"]
Ukuran tipikal: 150–200 MB.
Strategi 2: Production Dependency Only #
Gunakan:
npm ci --omit=dev
atau:
npm prune --omit=dev
Hasil:
- TypeScript compiler tidak ikut
- Ukuran turun drastis
Strategi 3: Distroless untuk TypeScript #
Dockerfile Distroless #
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json tsconfig.json ./
RUN npm ci
COPY src ./src
RUN npm run build
RUN npm prune --omit=dev
FROM gcr.io/distroless/nodejs20-debian12
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package.json .
EXPOSE 3000
CMD ["dist/index.js"]
Ukuran realistis: 60–110 MB.
Strategi 4: Bundling (Pendekatan Paling Bersih) #
Bundler seperti:
- esbuild
- tsup
mengubah aplikasi TypeScript menjadi satu file JavaScript.
Contoh: esbuild #
esbuild src/index.ts \
--bundle \
--platform=node \
--target=node20 \
--outfile=dist/app.js
Runtime Image #
FROM gcr.io/distroless/nodejs20-debian12
WORKDIR /app
COPY dist/app.js .
CMD ["app.js"]
Ukuran akhir: 30–60 MB.
Native Dependency: Jebakan Besar #
Package seperti:
bcryptsharpcanvas
Akan:
- membengkakkan image
- menyulitkan distroless
Solusi:
- gunakan pure JS alternative
- atau build khusus OS target
Source Map & Security #
Pastikan:
- source map tidak ikut production
- stack trace tetap meaningful
{
"sourceMap": false
}
Security & Production Readiness #
Non-root User #
Distroless sudah non-root by default.
Logging #
- STDOUT
- structured logging
Healthcheck #
Gunakan HTTP endpoint, bukan shell.
Perbandingan Strategi #
| Strategi | node_modules | Ukuran | Debugging | Rekomendasi |
|---|---|---|---|---|
| Multi-stage | Ada | Sedang | Mudah | Dev |
| Distroless | Ada | Kecil | Sulit | Prod |
| Bundling | Tidak ada | Sangat kecil | Menengah | API / Serverless |
Kapan Strategi Mana Digunakan? #
| Kondisi | Pilihan |
|---|---|
| API production | distroless |
| Microservice | bundling |
| Banyak native addon | alpine |
| Serverless | bundling |
Penutup #
TypeScript tidak bermasalah di Docker. Yang bermasalah adalah perlakuan TypeScript seperti JavaScript runtime biasa.
Jika fase build dan runtime dipisahkan dengan benar, TypeScript bisa:
- sekecil Go service
- lebih ringan dari Java
- setara distroless Python
Image kecil bukan tujuan—ia adalah konsekuensi dari build boundary yang bersih.
“Jika TypeScript kamu besar di Docker, kemungkinan besar yang kamu deploy bukan aplikasi—tapi toolchain.”