🐳 Dockerを使ってブログプラットフォームを構築する #5: Dockerfileを追加 + Clouderizedにデプロイ
簡単な一文で: Dockerfileを書き、イメージをビルドし、git push 1つでClouderizedにデプロイして、Flaskブログが自動HTTPSでライブになるようにし、サーバーの世話なしにする.
🔧 はじめに: Blogger移行のためのURL制御
Dockerに触れる前に、エピソード4の修正点の一つを今すぐ行う価値があります。
現在、私たちのプラットフォームはファイル名から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 |
私たちはcanonical_urlをフロントマターに追加し、それを二つのことに使用します。
- このプラットフォームが投稿を提供する実際のURL — それにより移行された投稿はそのBloggerのパスを保持します
-
<link rel="canonical">— このページが古いBlogger URLと同じコンテンツであることをGoogleに伝えます
更新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"
---
この不一致は意図的です:ファイル名と提供されるパスは異なることがあります。
app.py
にURLヘルパーを追加してください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']を見つけ、正しいマークダウンファイルをロードします.
ホームページのリンクを更新
イン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 name="description" content="{{ post.description }}">
{% if post.canonical_url %}
<link rel="canonical" href="{{ post.canonical_url }}">
{% endif %}
The {% if %}ガードは、canonical_urlがない投稿においてタグを省略可能に保ちます.
Frontmatterスキーマの更新
これがこれからの完全なFrontmatterスキーマです:
| フィールド | 使用目的: |
|---|---|
title |
<title> タグ、<h1> 投稿ページ上、投稿リスト |
date |
URL生成、並び替え、表示 |
description |
<meta name="description">、ホームページ上の要約 |
tags |
投稿ページ上のタグピル、最終的にEp12でタグページを設定 |
canonical_url |
ホームページリンク+<link rel="canonical">Blogger移行用(任意) |
投稿を移行する際、canonical_urlを元のBloggerURLに合わせて設定。
✅ ステップ1: ローカルですべてが正常に動作することを確認
Dockerに触れる前に、URLが変更された後もプラットフォームが正しく動作することを確認してください
仮想環境をアクティブにしてアプリを実行します
$ 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にプッシュする前に、リポジトリに属さないものをコミットしていないことを確認してください。プロジェクトのルートに.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をクラウド化されたユーザー名として、tioblogをプロジェクト名として使用します
アプリの場合はこちらを使用:
https://git.clouderized.com/<username>/<project>.git
1分から2分後、あなたのブログはこちらで公開されます:
https://davidtio-tioblog.clouderized.com
同じDockerfileで、今はHTTPSで公開中です。
カスタムドメインを追加
Clouderizedもカスタムドメインをサポートしています。このブログの場合、本番環境はblog.dtio.appで実行されています。
https://dash.clouderized.com- にアクセスして
tioblogアプリカードを開きます。 Domains- をクリックして、あなたのカスタムドメイン(例:
blog.dtio.app)を追加します。 - Clouderizedによって表示される
cfargotunnelのターゲットをコピーします - ドメイン用に
CNAMEレコードを作成し、それをcfargotunnelのターゲットに指針を立てます
DNSのプロパゲーションが完了した後、あなたのアプリケーションはあなたのドメインで提供され、Clouderizedは引き続きルーティングとHTTPSの処理を担当します
小規模クリエイタープラットフォームにとってこれが重要な理由は:
- 一つのデプロイ単位(あなたのDockerイメージ)を保持します
- 手動で行うリバースプロキシと証明書設定を回避します
- 同じGitワークフローでコンテンツとアプリの変更を送信できます
🧪 ステップ8: ライブかどうかを確認します
ブラウザを開いてライブURLを確認します
https://davidtio-tioblog.clouderized.com
ホームページが読み込まれ、投稿がリスト化され、.htmlの投稿URLが機能することを確認してください:
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パスを適切なマークダウンファイルに解決します
- ホームページリンクは
{{ post.path }} - にオプションのカノニカルタグが追加されました
post.html -
Dockerfile.dockerignore、.gitignoreが - アプリをコンテナ内で実行し、
docker build+docker run - のデプロイメントが Clouderized で公開 HTTPS ホスティングにプッシュされました。
- カスタムドメインは
Domainsにマッピングでき、DNS は提供されたcfargotunnelターゲットを指します。 - のデプロイフローはGitネイティブで、今後のシリーズ投稿のための公開操作を再現可能に保ちます
🚀 今後の予定
一つの問題:投稿を編集するたびに、変更を確認するためにイメージを再構築する必要があります。それはコンテンツ駆動型ブログの目的を無にします
次のエピソード:バインドマウント。私たちはマウントしますcontent/ フォルダを直接あなたのマシンからコンテナにドロップする——投稿を編集し、ページをリフレッシュし、すぐに変更を確認する。再ビルドは不要です.
これが役に立ったですか?ネットワークで共有するか、下のコメント欄にどうぞ.
SEO メタデータ
- タイトル: Dockerでブログプラットフォームを作る #5: Dockerfileを追加 + Clouderizedにデプロイ
- メタディスクリプション: Flaskのブログ用Dockerfileを作成し、イメージをビルドしてclouderized.comにデプロイします—git.clouderized.com/davidtio/tioblogにプッシュし、自動ビルド、自動ルーティング、自動HTTPSでdavidtio-tioblog.clouderized.comにデプロイします。












