diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..f000b86
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,41 @@
+# ----------------------------
+# Build stage
+# ----------------------------
+FROM python:3.11.0-alpine AS builder
+
+RUN apk add --no-cache \
+ # python3 \
+ # py3-pip \
+ git \
+ # build-base \
+ # curl
+
+ENV POETRY_VERSION=2.2.0
+RUN pip install --no-cache-dir "poetry==$POETRY_VERSION" # --break-system-packages is not needed
+
+ENV POETRY_VIRTUALENVS_CREATE=false
+ENV POETRY_NO_INTERACTION=1
+
+WORKDIR /app
+RUN git clone https://git.stefan.works/stefan/website.git .
+RUN poetry install --only main ## --no-root
+
+# RUN poetry run python -m scripts/patch_flask_frozen
+RUN poetry run patch-frozen
+RUN poetry run python freezer.py
+
+# ----------------------------
+# Runtime stage
+# ----------------------------
+FROM alpine:3.20
+
+RUN apk add --no-cache nginx
+
+RUN rm -rf /usr/share/nginx/html/* \
+ && mkdir -p /run/nginx
+
+COPY --from=builder /app/build /usr/share/nginx/html
+COPY container-nginx.conf /etc/nginx/nginx.conf
+
+EXPOSE 80
+CMD ["nginx", "-g", "daemon off;"]
diff --git a/build/rss.xml b/build/rss.xml
index a3bde5b..feba9dc 100644
--- a/build/rss.xml
+++ b/build/rss.xml
@@ -1,2 +1,2 @@
-Website of Stefan Friesehttps://stefan.workstesthttp://www.rssboard.org/rss-specificationpython-feedgenen-usSun, 20 Jul 2025 22:08:29 +0000Directory Writeup — TryHackMehttps://stefan.works/blog/Directory Writeup — TryHackMe/index.htmlhttps://stefan.works/blog/Directory Writeup — TryHackMe/index.htmlSun, 20 Jul 2025 00:00:00 +0200Restoring (NT)Backups From a SCSI Tape Drivehttps://stefan.works/blog/Restoring (NT)Backups From a SCSI Tape Drive/index.htmlhttps://stefan.works/blog/Restoring (NT)Backups From a SCSI Tape Drive/index.htmlWed, 27 Sep 2023 00:00:00 +0200The Joy of One-Linershttps://stefan.works/blog/The Joy of One-Liners/index.htmlhttps://stefan.works/blog/The Joy of One-Liners/index.htmlSun, 29 May 2022 00:00:00 +0200Keep It Simplehttps://stefan.works/blog/Keep It Simple/index.htmlhttps://stefan.works/blog/Keep It Simple/index.htmlTue, 01 Jun 2021 00:00:00 +0200
\ No newline at end of file
+Website of Stefan Friesehttps://stefan.workstesthttp://www.rssboard.org/rss-specificationpython-feedgenen-usMon, 22 Dec 2025 23:05:02 +0000Directory Writeup — TryHackMehttps://stefan.works/blog/Directory Writeup — TryHackMe/index.htmlhttps://stefan.works/blog/Directory Writeup — TryHackMe/index.htmlSun, 20 Jul 2025 00:00:00 +0200Restoring (NT)Backups From a SCSI Tape Drivehttps://stefan.works/blog/Restoring (NT)Backups From a SCSI Tape Drive/index.htmlhttps://stefan.works/blog/Restoring (NT)Backups From a SCSI Tape Drive/index.htmlWed, 27 Sep 2023 00:00:00 +0200The Joy of One-Linershttps://stefan.works/blog/The Joy of One-Liners/index.htmlhttps://stefan.works/blog/The Joy of One-Liners/index.htmlSun, 29 May 2022 00:00:00 +0200Keep It Simplehttps://stefan.works/blog/Keep It Simple/index.htmlhttps://stefan.works/blog/Keep It Simple/index.htmlTue, 01 Jun 2021 00:00:00 +0200
\ No newline at end of file
diff --git a/build/static/rss.xml b/build/static/rss.xml
index f32cf8a..a3bde5b 100644
--- a/build/static/rss.xml
+++ b/build/static/rss.xml
@@ -1,2 +1,2 @@
-Website of Stefan Friesehttps://stefan.workstesthttp://www.rssboard.org/rss-specificationpython-feedgenen-usWed, 11 Oct 2023 19:32:42 +0000Restoring (NT)Backups From a SCSI Tape Drivehttps://stefan.works/blog/Restoring (NT)Backups From a SCSI Tape Drive/index.htmlhttps://stefan.works/blog/Restoring (NT)Backups From a SCSI Tape Drive/index.htmlWed, 27 Sep 2023 00:00:00 +0200The Joy of One-Linershttps://stefan.works/blog/The Joy of One-Liners/index.htmlhttps://stefan.works/blog/The Joy of One-Liners/index.htmlSun, 29 May 2022 00:00:00 +0200Keep It Simplehttps://stefan.works/blog/Keep It Simple/index.htmlhttps://stefan.works/blog/Keep It Simple/index.htmlTue, 01 Jun 2021 00:00:00 +0200
\ No newline at end of file
+Website of Stefan Friesehttps://stefan.workstesthttp://www.rssboard.org/rss-specificationpython-feedgenen-usSun, 20 Jul 2025 22:08:29 +0000Directory Writeup — TryHackMehttps://stefan.works/blog/Directory Writeup — TryHackMe/index.htmlhttps://stefan.works/blog/Directory Writeup — TryHackMe/index.htmlSun, 20 Jul 2025 00:00:00 +0200Restoring (NT)Backups From a SCSI Tape Drivehttps://stefan.works/blog/Restoring (NT)Backups From a SCSI Tape Drive/index.htmlhttps://stefan.works/blog/Restoring (NT)Backups From a SCSI Tape Drive/index.htmlWed, 27 Sep 2023 00:00:00 +0200The Joy of One-Linershttps://stefan.works/blog/The Joy of One-Liners/index.htmlhttps://stefan.works/blog/The Joy of One-Liners/index.htmlSun, 29 May 2022 00:00:00 +0200Keep It Simplehttps://stefan.works/blog/Keep It Simple/index.htmlhttps://stefan.works/blog/Keep It Simple/index.htmlTue, 01 Jun 2021 00:00:00 +0200
\ No newline at end of file
diff --git a/docker-nginx.conf b/docker-nginx.conf
new file mode 100644
index 0000000..dea446f
--- /dev/null
+++ b/docker-nginx.conf
@@ -0,0 +1,25 @@
+worker_processes 1;
+
+events {
+ worker_connections 1024;
+}
+
+http {
+ include /etc/nginx/mime.types;
+ default_type application/octet-stream;
+
+ sendfile on;
+ keepalive_timeout 65;
+
+ server {
+ listen 80;
+ server_name _;
+
+ root /usr/share/nginx/html;
+ index index.html;
+
+ location / {
+ try_files $uri $uri/ =404;
+ }
+ }
+}
diff --git a/pyproject.toml b/pyproject.toml
index 0eaf8a3..397fb79 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -21,3 +21,6 @@ toml = "*"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
+
+[tool.poetry.scripts]
+patch-frozen = "scripts.patch_flask_frozen:patch_flask_frozen"
diff --git a/scripts/__init__.py b/scripts/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/scripts/patch_flask_frozen.py b/scripts/patch_flask_frozen.py
new file mode 100644
index 0000000..f27141e
--- /dev/null
+++ b/scripts/patch_flask_frozen.py
@@ -0,0 +1,38 @@
+from pathlib import Path
+
+
+def patch_flask_frozen():
+ try:
+ import flask_frozen
+ except ImportError:
+ print("flask_frozen is not installed.")
+
+ return
+
+ init_path = Path(flask_frozen.__file__).parent / "__init__.py"
+
+ if not init_path.exists():
+ print("Could not find flask_frozen __init__.py")
+
+ return
+
+ with open(init_path, "r") as f:
+ lines = f.readlines()
+
+ changed = False
+
+ for i, line in enumerate(lines):
+ if "url_encoding = self.app.url_map.charset" in line:
+ lines[i] = " url_encoding = 'utf-8' # patched charset\n"
+ changed = True
+
+ if changed:
+ with open(init_path, "w") as f:
+ f.writelines(lines)
+ print("✅ flask_frozen patched successfully.")
+ else:
+ print("⚠️ Patch already applied or target line not found.")
+
+
+if __name__ == "__main__":
+ patch_flask_frozen()
diff --git a/static/rss.xml b/static/rss.xml
index a3bde5b..feba9dc 100644
--- a/static/rss.xml
+++ b/static/rss.xml
@@ -1,2 +1,2 @@
-Website of Stefan Friesehttps://stefan.workstesthttp://www.rssboard.org/rss-specificationpython-feedgenen-usSun, 20 Jul 2025 22:08:29 +0000Directory Writeup — TryHackMehttps://stefan.works/blog/Directory Writeup — TryHackMe/index.htmlhttps://stefan.works/blog/Directory Writeup — TryHackMe/index.htmlSun, 20 Jul 2025 00:00:00 +0200Restoring (NT)Backups From a SCSI Tape Drivehttps://stefan.works/blog/Restoring (NT)Backups From a SCSI Tape Drive/index.htmlhttps://stefan.works/blog/Restoring (NT)Backups From a SCSI Tape Drive/index.htmlWed, 27 Sep 2023 00:00:00 +0200The Joy of One-Linershttps://stefan.works/blog/The Joy of One-Liners/index.htmlhttps://stefan.works/blog/The Joy of One-Liners/index.htmlSun, 29 May 2022 00:00:00 +0200Keep It Simplehttps://stefan.works/blog/Keep It Simple/index.htmlhttps://stefan.works/blog/Keep It Simple/index.htmlTue, 01 Jun 2021 00:00:00 +0200
\ No newline at end of file
+Website of Stefan Friesehttps://stefan.workstesthttp://www.rssboard.org/rss-specificationpython-feedgenen-usMon, 22 Dec 2025 23:05:02 +0000Directory Writeup — TryHackMehttps://stefan.works/blog/Directory Writeup — TryHackMe/index.htmlhttps://stefan.works/blog/Directory Writeup — TryHackMe/index.htmlSun, 20 Jul 2025 00:00:00 +0200Restoring (NT)Backups From a SCSI Tape Drivehttps://stefan.works/blog/Restoring (NT)Backups From a SCSI Tape Drive/index.htmlhttps://stefan.works/blog/Restoring (NT)Backups From a SCSI Tape Drive/index.htmlWed, 27 Sep 2023 00:00:00 +0200The Joy of One-Linershttps://stefan.works/blog/The Joy of One-Liners/index.htmlhttps://stefan.works/blog/The Joy of One-Liners/index.htmlSun, 29 May 2022 00:00:00 +0200Keep It Simplehttps://stefan.works/blog/Keep It Simple/index.htmlhttps://stefan.works/blog/Keep It Simple/index.htmlTue, 01 Jun 2021 00:00:00 +0200
\ No newline at end of file