🐳 使用 Docker 建立部落格平台 #5:添加 Dockerfile + 部署到 Clouderized
快速一行指令: 寫一個 Dockerfile,建立一個影像,並使用一個 git push 部署到 Clouderized,讓你的 Flask 部落格自動啟動 HTTPS 並無需伺服器照看.
🔧 開始前:為 Blogger 迁移進行 URL 控制
在接觸 Docker 之前,有一個來自第四集的修復措施現在值得執行。
目前我們的平台從檔案名稱建立 URL:hey-markdown.md 變成 /2026/04/hey-markdown.html。這對於新帖子有效,但對於遷移過來的 Blogger URL 則無效:
| Blogger URL | 基於檔案名稱的 URL |
|---|---|
/2026/03/docker-rootless-on-ubuntu-2026-guide.html |
/2026/03/docker-rootless-ubuntu.html |
我們在 frontmatter 中添加 canonical_url 並用它來做兩件事:
- 此平台實際為文章服務的 URL — 如此則遷移的文章能保留他們的 Blogger 路徑
-
<link rel="canonical">— 這會告訴 Google 這頁面與舊 Blogger URL 的內容相同
更新content/posts/hey-markdown.md:
---
title: "Hey Markdown"
date: 2026-04-25
description: "The first post written in Markdown — no more writing HTML by hand."
tags: [meta, blog]
canonical_url: "https://blog.dtio.app/2026/04/old-markdown.html"
---
這種不匹配是故意的:檔案名稱與服務路徑可以不同。
加入一個 URL Helper 到app.py
打開app.py. 在最上面添加這個匯入:
from urllib.parse import urlparse
然後再加一個get_post_path()以下函數parse_post():
def get_post_path(meta):
"""Determine the URL path for a post from its canonical_url."""
if meta.get('canonical_url'):
return urlparse(meta['canonical_url']).path
# Fallback: auto-generate from date + slug
slug = meta.get('slug', 'unknown')
date = meta.get('date', '')
if hasattr(date, 'year'):
return f'/{date.year}/{date.month:02d}/{slug}.html'
return f'/{slug}.html'
這個輔助工具從中提取 URL 路徑canonical_url,帶有日期+縮略名的備用方案.
更新 get_all_posts()
在現有的 get_all_posts() 函數中,添加路徑欄位:
def get_all_posts():
posts = []
posts_dir = 'content/posts'
for filename in os.listdir(posts_dir):
if not filename.endswith('.md'):
continue
filepath = os.path.join(posts_dir, filename)
post = parse_post(filepath)
post['slug'] = filename[:-3]
post['path'] = get_post_path(post)
posts.append(post)
posts.sort(key=lambda p: p.get('date', ''), reverse=True)
return posts
每篇帖子現在有一個 path 欄位 — 它應該被服務的確切 URL.
更新路徑
尋找現有的路徑在app.py:
@app.route('/<int:year>/<int:month>/<slug>')
def post(year, month, slug):
filepath = f'content/posts/{slug}.md'
if not os.path.exists(filepath):
abort(404)
post = parse_post(filepath)
return render_template('post.html', post=post)
用這個動態查詢替換它:
@app.route('/<int:year>/<int:month>/<path:slug>.html')
def post(year, month, slug):
target_path = f'/{year}/{month:02d}/{slug}.html'
all_posts = get_all_posts()
matching_post = next((p for p in all_posts if p['path'] == target_path), None)
if not matching_post:
abort(404)
post_data = parse_post(f'content/posts/{matching_post["slug"]}.md')
post_data['path'] = matching_post['path']
return render_template('post.html', post=post_data)
這個路徑匹配 .html URL,找到匹配的 post['path'],並載入正確的 markdown 檔案.
更新首頁連結
在templates/index.html中,將手動構建的 URL 替換為文章的路徑:
<a href="{{ post.path }}">{{ post.title }}</a>
而對於"閱讀更多"鏈接:
<a href="{{ post.path }}" class="text-teal-400 text-sm hover:text-teal-300 font-medium transition-colors duration-200">
Read more →
</a>
添加標準鏈接標籤
在templates/post.html中,在內部添加標準鏈接標籤<head> 在 meta 描述之後:
<meta name="description" content="{{ post.description }}">
{% if post.canonical_url %}
<link rel="canonical" href="{{ post.canonical_url }}">
{% endif %}
The {% if %} 開啟,讓標籤對沒有 canonical_url 的文章是可選的。
更新 Frontmatter Schema
這是目前未來的全 Frontmatter Schema:
| Field | 用於 |
|---|---|
title |
<title> 標籤,<h1> 在文章頁面,文章列表 |
date |
URL 生成,排序,顯示 |
description |
<meta name="description">,文章縮略預覽在首頁 |
tags |
文章頁面上的標籤藥丸,最終在Ep12中標籤頁面 |
canonical_url |
首頁連結 + <link rel="canonical"> 用於Blogger遷移(可選) |
在遷移文章時,將 canonical_url 設為與原Blogger URL一致。
✅ 步驟 1:在地端確認一切正常
在觸及 Docker 之前,確保平台在 URL 更改後仍然正確運行
啟動您的 venv 並運行應用程式:
$ source venv/bin/activate
$ python app.py
造訪 http://localhost:8000 並檢查:
- 首頁 — 列出貼文,連結指向正確的路徑
-
貼文頁面 — 正確渲染,
<link rel="canonical">存在于 HTML 源碼中(檢視原始碼並搜尋canonical) - 標籤藥丸 — 在貼文頁面上正確顯示
一旦本地一切正常運作,我們就準備好將其容器化了。
📦 第 2 步:添加一個 .dockerignore
在撰寫 Dockerfile 之前,告訴 Docker 要從影像中排除什麼。在專案根目錄中建立 .dockerignore:
venv/
__pycache__/
*.pyc
.git/
.gitignore
這能讓影像保持輕量,並避免發送本地環境檔案.
📄 第 3 步:撰寫 Dockerfile
建立Dockerfile 在專案根目錄:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["python", "app.py"]
為何這個順序很重要:
- 先複製
requirements.txt以便依賴安裝可以被快取 - 之後再複製應用程式碼,以便常規程式碼編輯能快速重新建構
🔨 第 4 步:建立影像
確保你位於專案根目錄(Dockerfile所在地):
$ docker build -t tioblog .
完成後,確認圖片是否存在:
$ docker images tioblog
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
tioblog:latest 05dbc52b9852 231MB 55.6MB
🚀 第5步:執行它
$ docker run -p 8000:8000 tioblog
造訪http://localhost:8000。部落格載入 — 和以前一樣,但現在在容器內運行.
📝 步驟 6: 添加 .gitignore
在推送至 Clouderized 之前,確保你沒有提交不應該存入 repo 的東西。在專案根目錄中建立.gitignore:
venv/
__pycache__/
*.pyc
.env
這能讓你的虛擬環境、快取檔案以及任何本地設定都離開版本控制
🌐 步驟 7: 部署到 Clouderized
Clouderized 是這系列中的部署目標,因為它專為簡單的容器應用程式發送進行了優化:推送到 Git、自動建構、自動部署、預設啟用 HTTPS
對於這篇部落格,部署是:
$ git init
$ git add .
$ git commit -m "Initial commit for tioblog"
$ git branch -M main
$ git remote add origin https://git.clouderized.com/davidtio/tioblog.git
$ git push -u origin main
https://git.clouderized.com/davidtio/tioblog.git 使用 davidtio 作為 Clouderized 使用者名稱,並使用 tioblog 作為專案名稱.
對於您的應用程式,請使用:
https://git.clouderized.com/<username>/<project>.git
一分鐘或兩分鐘後,您的部落格在:
https://davidtio-tioblog.clouderized.com
相同 Dockerfile,現已以 HTTPS 公開執行.
添加您的自訂域
Clouderized 也支援自訂域。對於這個部落格,生產環境運行在 blog.dtio.app.
- 前往
https://dash.clouderized.com - 打開
tioblog應用程式卡片 - 點擊
Domains - 添加您的自訂域(例如
blog.dtio.app) - 複製
cfargotunnel由Clouderized顯示的目標 - 為您的域名建立一個
CNAME記錄,並將其指向cfargotunnel目標
DNS傳播後,您的應用程式會在您自己的域名上提供服務,而Clouderized則繼續處理路由和HTTPS
對小型創作者平台而言,這為何重要:
- 你保持一個部署單位(你的 Docker 映像)
- 你避免手動維護反向代理和證書設定
- 你可以使用相同的 Git 工作流程發布內容和應用程式變更
🧪 步驟 8:驗證它已啟動
打開你的瀏覽器並檢查實時 URL:
https://davidtio-tioblog.clouderized.com
確認首頁載入、文章已列出,且.html的文章連結有效:
https://davidtio-tioblog.clouderized.com/2026/04/hey-markdown.html
這與Blogger風格的URL相符,以便更順暢地遷移.
另外請確認HTTPS已啟用且憑證有效。在Clouderized上這是自動完成的,因此您可以專注於內容和產品工作,而不是基礎設施任務。
✅ 你已建立的內容
tiohub-blog/
├── Dockerfile ← new
├── .dockerignore ← new
├── app.py (get_post_path, post['path'] in get_all_posts, route adds post['path'])
├── requirements.txt
├── static/
│ └── js/
│ ├── tailwind.config.js
│ └── code-blocks.js
├── templates/
│ ├── index.html (links use {{ post.path }} instead of manual URLs)
│ └── post.html (<link rel="canonical"> added)
└── content/
└── posts/
├── hey-markdown.md (canonical_url added to frontmatter)
└── second-post.md
變更內容:
-
canonical_url和post.path現在驅動穩定的後續 URL - 動態路徑解析 URL 路徑到正確的 markdown 檔案
- 首頁連結使用
{{ post.path }} - 選擇性加入规范標籤到
post.html -
Dockerfile.dockerignore,以及.gitignore新增了 - 應用程式在容器內運行,搭配
docker build與docker run - 部署已推送至Clouderized,供公眾透過HTTPS主機使用
- 的自訂域名可在
Domains中設定,DNS指向所提供的cfargotunnel目標 - 部署流程是 Git 為本的,這讓發布操作對未來的系列文章保持可重複
🚀 即將推出
一個問題:每當你編輯文章時,你必須重新建立圖像才能看到變化。這破壞了內容驅動型博客的初衷
下一集:綁定掛載。我們將掛載content/ 資料夾從您的電腦直接複製到容器中 — 編輯一篇帖子、刷新頁面、立即看到變更。不需要重新構建.
您覺得這有幫助嗎? 與您的網絡分享或留下評論.
SEO 元數據
- 標題: 使用 Docker 建立部落格平台 #5:添加 Dockerfile + 部署到 Clouderized
- Meta 描述: 請為您的 Flask 博客編寫一個 Dockerfile,建立影像,並部署到 clouderized.com — 推送到 git.clouderized.com/davidtio/tioblog,自動建立,自動路由,並自動 HTTPS 在 davidtio-tioblog.clouderized.com。












