From 685d336de797b582d20e3a4b9c473dbeec7a033a Mon Sep 17 00:00:00 2001
From: janik6n <janikarh@gmail.com>
Date: Mon, 23 Dec 2024 20:36:20 +0200
Subject: [PATCH] Added container build + run

---
 .dockerignore     | 19 +++++++++++++++++++
 CHANGELOG.md      | 18 ++++++++++++++++++
 Dockerfile        | 32 ++++++++++++++++++++++++++++++++
 README.md         | 25 +++++++++++++++++++------
 package-lock.json |  4 ++--
 package.json      | 10 ++++++----
 6 files changed, 96 insertions(+), 12 deletions(-)
 create mode 100644 .dockerignore
 create mode 100644 Dockerfile

diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..4f8a436
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,19 @@
+.DS_Store
+*.log
+**/.env
+.git
+.gitignore
+*.log
+**/node_modules
+**/npm-debug.log
+Dockerfile
+.dockerignore
+**/dist
+**/eslint.config.mjs
+**/.prettierrc.json
+**/vitest.config.js
+**/vitest.workspace.js
+**/*.md
+.github
+**/docs
+.vscode
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6547aeb..3b7d094 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,24 @@ All notable changes to this template will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [1.1.0] - 2024-12-23
+
+### Added
+
+- Build and run the app as Docker container.
+
+### Changed
+
+### Deprecated
+
+### Fixed
+
+### Removed
+
+### Security
+
+### Internal
+
 ## [1.0.0] - 2024-12-22
 
 This is a major rewrite of this template. Many tools have been changed.
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..8227d30
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,32 @@
+# Node build image
+FROM node:22.12.0-bookworm AS builder
+# Install Ubuntu packages + dumb-init
+RUN apt-get update && apt-get install -y --no-install-recommends dumb-init
+# Set working directory
+WORKDIR /app
+# Copy package.json and package-lock.json
+COPY ./package*.json .
+# Install NPM dependencies
+RUN npm install
+# Copy source files
+COPY . .
+# Build the app (TS > JS + treeshake + bundle + minify with Esbuild)
+RUN npm run build
+
+# Production image
+FROM node:22.12.0-bookworm-slim AS production
+# Install ca-certificates
+RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/*
+# Set Node.js environment to production
+ENV NODE_ENV=production
+# Copy dumb-init from builder
+COPY --from=builder /usr/bin/dumb-init /usr/bin/dumb-init
+# Do not run as root, create a new user
+USER node
+WORKDIR /app
+# Copy bundled app with source map from builder container
+COPY --chown=node:node --from=builder /app/dist .
+# Set environment variable as an example
+ENV CONTAINERIZED=true
+# Run the app
+CMD ["sh", "-c", "dumb-init node index.mjs"]
diff --git a/README.md b/README.md
index 7ac3322..06b04eb 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,7 @@
 ![Prettier](https://shields.io/badge/Prettier-f8bc45?logo=prettier&logoColor=FFF&style=flat-square)
 ![Vitest](https://shields.io/badge/Vitest-acd268?logo=vitest&logoColor=FFF&style=flat-square)
 ![Esbuild](https://shields.io/badge/Esbuild-ffcf00?logo=esbuild&logoColor=FFF&style=flat-square)
+![Docker](https://shields.io/badge/Docker-1D63ED?logo=Docker&logoColor=FFF&style=flat-square)
 ![VS Code](https://shields.io/badge/VS%20Code-0078d4?style=flat-square)
 ![GitHub](https://shields.io/badge/GitHub-000?logo=github&logoColor=FFF&style=flat-square)
 [![Build Status](https://github.com/janik6n/typescript-starter/workflows/Code%20quality%20checks/badge.svg)](https://github.com/janik6n/typescript-starter/actions)
@@ -17,7 +18,8 @@ This is my batteries included TypeScript starter updated for 2025, with:
 - ⚙️ NPM package manager
 - 🚥 testing with [Vitest](https://vitest.dev/)
 - 📦 production bundling with [esbuild](https://esbuild.github.io/)
-- ⚗️ Code linting & formatting with [ESLint](https://eslint.org/) + [Prettier](https://prettier.io/)
+- 🐳 optional building of production ready [Docker](https://www.docker.com/) image
+- ⚗️ Code linting & formatting with [ESLint](https://eslint.org/) & [Prettier](https://prettier.io/)
 - 🔬 [VS Code](https://code.visualstudio.com/) configuration for debugging
 - 🔥 hot reloading
 - 🔁 GitHub Actions workflow to run code quality checks and tests
@@ -25,7 +27,7 @@ This is my batteries included TypeScript starter updated for 2025, with:
 ## ✅ Prerequisites
 
 - Make sure you have Node.js 22 installed. This is built, configured and tested with `Node.js 22`.
-- Install VS Code, and the following extensions:
+- If you want to use VS Code, make sure the following extensions are installed:
   - [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)
   - [Vitest](https://marketplace.visualstudio.com/items?itemName=vitest.explorer)
 
@@ -34,7 +36,7 @@ This is my batteries included TypeScript starter updated for 2025, with:
 How to use this template?
 
 1. Make sure prerequisites are met.
-2. Copy the repository as zip so you don't have to deal with git history.
+2. Download the repository as zip so you don't have to deal with git history of this repository.
 3. Delete `package-lock.json`.
 3. Replace `janik6n` in `package.json` with your own username, along with other info.
 3. Install dependencies by running `npm install` on the project root directory.
@@ -78,11 +80,17 @@ Build command explained: `"build": "rimraf ./dist && npx tsc --noEmit && node bu
 
 Sometimes it is necessary to see the built app with just transpiling without bundling. This can be accomplished with `npm run build:tsc`.
 
-*Future plans: build as Docker 🐳 container with `npm run build:container`.*
+### ⚙️ Run production bundle
 
-### ⚙️ Serve production bundle
+Run the built app with `npm run start`.
 
-Serve the built app with `npm run start`.
+### 🐳 Build as container
+
+Run `npm run build:container` to build the app as Docker container. Multi-stage build is used to minimize the production image size. Debian-based image is used to minimize the risk of compatibility issues.
+
+### 🐳 Run as container
+
+Run the containerized app with `npm run start:container`.
 
 ## 🐛 Known issues
 
@@ -123,6 +131,11 @@ None as of now. 🦗
 
 - https://esbuild.github.io/
 
+### Docker & Node.js
+
+- https://hub.docker.com/_/node
+- https://snyk.io/blog/choosing-the-best-node-js-docker-image/
+
 ### GitHub
 
 - https://github.com/features/actions
diff --git a/package-lock.json b/package-lock.json
index 5ec1b8e..1690310 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "typescript-starter",
-  "version": "1.0.0",
+  "version": "1.1.0",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "typescript-starter",
-      "version": "1.0.0",
+      "version": "1.1.0",
       "license": "MIT",
       "devDependencies": {
         "@eslint/js": "^9.17.0",
diff --git a/package.json b/package.json
index 96fd31b..d59c704 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "typescript-starter",
   "description": "TypeScript Starter",
-  "version": "1.0.0",
+  "version": "1.1.0",
   "homepage": "https://github.com/janik6n/typescript-starter#readme",
   "repository": {
     "type": "git",
@@ -22,7 +22,7 @@
   "scripts": {
     "build": "rimraf ./dist && npx tsc --noEmit && node build.js",
     "build:tsc": "rimraf ./dist && tsc",
-    "build:container": "echo \"TODO: Not implemented yet\" && exit 1",
+    "build:container": "docker build -t typescript-starter:1.1.0 .",
     "dev": "tsx src/index.ts",
     "dev:watch": "tsx watch src/index.ts",
     "format": "prettier . --write",
@@ -34,7 +34,8 @@
     "test:cov": "vitest run --coverage.enabled=true",
     "test:watch": "vitest --coverage.enabled=false --project='unit-integration'",
     "test:ci": "vitest run --coverage.enabled=true --project='unit-integration'",
-    "start": "node ./dist/index.mjs"
+    "start": "node ./dist/index.mjs",
+    "start:container": "docker run -it --rm typescript-starter:1.1.0"
   },
   "engines": {
     "node": ">=22.0.0"
@@ -53,5 +54,6 @@
     "typescript": "^5.7.2",
     "typescript-eslint": "^8.18.1",
     "vitest": "^2.1.8"
-  }
+  },
+  "dependencies": {}
 }
\ No newline at end of file