diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..260886a
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,15 @@
+node_modules
+.next
+.git
+.gitignore
+data
+*.tsbuildinfo
+next-env.d.ts
+.env*
+README.md
+Dockerfile
+.dockerignore
+docker-compose.yml
+npm-debug.log*
+.pnpm-debug.log*
+.DS_Store
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..48a327c
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,45 @@
+# syntax=docker/dockerfile:1
+
+# ---- deps: install node_modules (native build tools for better-sqlite3) ----
+FROM node:22-bookworm-slim AS deps
+WORKDIR /app
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ python3 make g++ \
+ && rm -rf /var/lib/apt/lists/*
+RUN corepack enable
+COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
+RUN pnpm install --frozen-lockfile
+
+# ---- builder: compile the Next.js standalone server ----
+FROM node:22-bookworm-slim AS builder
+WORKDIR /app
+RUN corepack enable
+COPY --from=deps /app/node_modules ./node_modules
+COPY . .
+ENV NEXT_TELEMETRY_DISABLED=1
+RUN pnpm build
+
+# ---- runner: minimal runtime image ----
+FROM node:22-bookworm-slim AS runner
+WORKDIR /app
+ENV NODE_ENV=production \
+ NEXT_TELEMETRY_DISABLED=1 \
+ PORT=3000 \
+ HOSTNAME=0.0.0.0
+
+# Non-root user; owns the data volume so SQLite can write.
+RUN addgroup --system --gid 1001 nodejs \
+ && adduser --system --uid 1001 nextjs
+
+# Standalone output bundles the traced node_modules (incl. the better-sqlite3
+# native binary), so no install/rebuild is needed here.
+COPY --from=builder /app/.next/standalone ./
+COPY --from=builder /app/.next/static ./.next/static
+COPY --from=builder /app/public ./public
+
+RUN mkdir -p /app/data && chown -R nextjs:nodejs /app/data
+VOLUME ["/app/data"]
+USER nextjs
+
+EXPOSE 3000
+CMD ["node", "server.js"]
diff --git a/README.md b/README.md
index 39a01bd..742d060 100644
--- a/README.md
+++ b/README.md
@@ -1,50 +1,163 @@
-This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
+
-## Getting Started
+# ๐น๏ธ RetroBlog
-First, run the development server:
+**A self-hosted blog engine with swappable OS / game-console skins.**
+
+Write Markdown posts, flip between retro themes (Windows, PS1, PS2, PS3, Wii,
+NDS, Dreamcast, JV2002โฆ), and run the whole thing from a single SQLite file.
+
+
+
+---
+
+## Features
+
+- **Markdown posts** โ create / edit / delete with slugs, tags, excerpts, dates.
+- **Swappable skins** โ pick the default theme; optionally let visitors switch.
+- **Admin panel** โ single-password login, signed session cookie.
+- **Import / export** โ JSON backup of settings and posts.
+- **Zero external services** โ data lives in one SQLite file (`data/blog.db`).
+
+Stack: Next.js 16 (App Router) ยท React 19 ยท better-sqlite3 ยท marked.
+
+---
+
+## Quick start (development)
```bash
-npm run dev
-# or
-yarn dev
-# or
+pnpm install
pnpm dev
-# or
-bun dev
```
-Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
+Open . Admin panel at
+(default password `admin` in dev).
-You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
+---
+
+## Configuration
+
+All config is environment variables. Copy `.env.example` to `.env` and edit.
+
+| Variable | Required | Default | Purpose |
+| ---------------------- | -------- | ---------------- | --------------------------------------------------------- |
+| `ADMIN_PASSWORD` | **prod** | `admin` | Password for `/admin`. **Set this in production.** |
+| `ADMIN_SESSION_SECRET` | **prod** | `ADMIN_PASSWORD` | Signs the admin session cookie. Use a long random string. |
+| `PORT` | no | `3000` | Port the server listens on. |
+
+Generate a secret:
+
+```bash
+openssl rand -hex 32
+```
+
+**Data** is stored in `./data/blog.db` (created automatically). Back up that
+directory to back up the whole site.
+
+---
+
+## Deploy โ Docker (recommended)
+
+Needs Docker + the Compose plugin.
+
+```bash
+# 1. Set production secrets
+cp .env.example .env
+$EDITOR .env # set ADMIN_PASSWORD and ADMIN_SESSION_SECRET
+
+# 2. Build and run
+docker compose up -d --build
+```
+
+Site is live on . The database persists in the
+`retroblog-data` Docker volume across rebuilds.
+
+Common operations:
+
+```bash
+docker compose logs -f # tail logs
+docker compose down # stop (keeps the data volume)
+docker compose up -d --build # update after pulling new code
+```
+
+**Back up the database:**
+
+```bash
+docker compose exec retroblog sh -c 'cat /app/data/blog.db' > backup-$(date +%F).db
+```
+
+> Put a TLS-terminating reverse proxy (Caddy, nginx, Traefik) in front for
+> HTTPS in production. RetroBlog speaks plain HTTP on `PORT`.
+
+---
+
+## Deploy โ bare metal
+
+Needs Node.js 22+ and `pnpm` (`corepack enable`). A C toolchain
+(`build-essential`, `python3`) is required once, to compile better-sqlite3.
+
+```bash
+# 1. Install deps and build
+pnpm install --frozen-lockfile
+pnpm build
+
+# 2. Set production secrets
+export ADMIN_PASSWORD='your-strong-password'
+export ADMIN_SESSION_SECRET="$(openssl rand -hex 32)"
+
+# 3. Start
+pnpm start # serves on PORT (default 3000)
+```
+
+### Run it as a service (systemd)
+
+`/etc/systemd/system/retroblog.service`:
+
+```ini
+[Unit]
+Description=RetroBlog
+After=network.target
+
+[Service]
+Type=simple
+WorkingDirectory=/opt/retroblog
+ExecStart=/usr/bin/pnpm start
+Restart=on-failure
+Environment=NODE_ENV=production
+Environment=PORT=3000
+Environment=ADMIN_PASSWORD=your-strong-password
+Environment=ADMIN_SESSION_SECRET=your-long-random-secret
+User=www-data
+Group=www-data
+
+[Install]
+WantedBy=multi-user.target
+```
+
+```bash
+sudo systemctl daemon-reload
+sudo systemctl enable --now retroblog
+```
+
+The SQLite database lives in `WorkingDirectory/data/blog.db` โ make sure the
+service `User` can write there, and back up that file.
+
+---
## Admin panel
-Visit [http://localhost:3000/admin](http://localhost:3000/admin) and sign in with
-`ADMIN_PASSWORD` (see `.env.example`; defaults to `admin` in dev). From there you can:
+Sign in at `/admin` with `ADMIN_PASSWORD`. From there:
-- **Posts** โ full create / edit / delete with Markdown bodies, slugs, tags, and dates.
-- **Settings** โ branding (title, subtitle, footer, version), the default theme for
+- **Posts** โ full create / edit / delete with Markdown bodies, slugs, tags, dates.
+- **Settings** โ branding (title, subtitle, footer, version), default theme for
new visitors, whether the public theme switcher is shown, and which skins it offers.
-- **Import / export** โ download a JSON backup of settings and/or posts, and import
- one back (posts can replace or append).
+- **Import / export** โ download a JSON backup of settings and/or posts, and
+ import one back (posts can replace or append).
-Auth is a single password kept in `ADMIN_PASSWORD`, with a signed session cookie
-(`ADMIN_SESSION_SECRET`). All `/admin/*` routes are gated by middleware.
+All `/admin/*` routes are gated by middleware behind the signed session cookie.
-This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
+---
-## Learn More
+## License
-To learn more about Next.js, take a look at the following resources:
-
-- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
-- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
-
-You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
-
-## Deploy on Vercel
-
-The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
-
-Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
+See repository.
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..c894029
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,18 @@
+services:
+ retroblog:
+ build: .
+ image: retroblog:latest
+ container_name: retroblog
+ restart: unless-stopped
+ ports:
+ - "3000:3000"
+ environment:
+ # CHANGE THESE before exposing to the internet.
+ ADMIN_PASSWORD: ${ADMIN_PASSWORD:-admin}
+ ADMIN_SESSION_SECRET: ${ADMIN_SESSION_SECRET:-change-me-to-a-long-random-string}
+ volumes:
+ # Persist the SQLite database across container rebuilds.
+ - retroblog-data:/app/data
+
+volumes:
+ retroblog-data:
diff --git a/next.config.ts b/next.config.ts
index e9ffa30..263be30 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -1,7 +1,9 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
- /* config options here */
+ // Emit a self-contained server in .next/standalone for slim Docker images
+ // and simple bare-metal deploys.
+ output: "standalone",
};
export default nextConfig;