Mix Space + Shiro 完全コンテナ化デプロイガイド
Mix Space は、フロントエンドとバックエンド分離アーキテクチャを採用したモダンな個人スペースのバックエンドです。Shiro はその公式対応の高品質なフロントエンドテーマで、Next.js をベースに構築されており、ミニマルなデザインと滑らかなアニメーションで知られています。両者を組み合わせることで、記事の執筆や日常の近況投稿を一体化し、究極のパフォーマンスと視覚的な美しさを兼ね備えた個人ブログシステムを構築できます。——Gemini
以前はHexoを使っていましたが、更新の手間と機能の少なさに悩んでいました(セルフホスティングを好んでいて、PaaSはあまり好きではないので)。たった一文字の更新でも、ビルドしてサーバーに再アップロードする必要があり、断片的な執筆が非常に困難でした。
Mix SpaceとShiroは相互補完的で、それぞれバックエンド(Core)とフロントエンド(Theme)です。他にもフロントエンドテーマがあります(例えば Kami や Yun)。本記事ではMix Space(バックエンド)とShiro(フロントエンド)のみ解説します。
バックエンドのデプロイは、公式が提供するビルド済みの Dockerイメージをそのまま使用できます。Shiroフロントエンドはビルド済みDockerイメージを使うこともできますし、自分でDockerイメージをビルドすることもできます。自前ビルドではフロントエンドページの深いカスタマイズが可能です(2つ以上の届出番号を表示する必要がある場合は、自前ビルドが必須です)。
前置きはこのくらいにして、始めましょう!
Mix Spaceのデプロイ
イメージの取得
バックエンドのデプロイはDocker Composeでイメージを取得するだけで実行できます
cd && mkdir -p mx-space/core && cd $_
nano docker-compose.yml
docker-compose.yml に以下を記述します:
services:
app:
container_name: mx-server
image: innei/mx-server:latest
environment:
- TZ=Asia/Shanghai
- NODE_ENV=production
- DB_HOST=mongo
- REDIS_HOST=redis
- ALLOWED_ORIGINS=localhost
- JWT_SECRET=YOUR_SUPER_SECURED_JWT_SECRET_STRING
volumes:
- ./data/mx-space:/root/.mx-space
ports:
- '2333:2333'
depends_on:
- mongo
- redis
networks:
- mx-space
restart: unless-stopped
healthcheck:
test: [CMD, curl, -f, 'http://127.0.0.1:2333/api/v2/ping']
interval: 1m30s
timeout: 30s
retries: 5
start_period: 30s
mongo:
container_name: mongo
image: mongo:7
volumes:
- ./data/db:/data/db
networks:
- mx-space
restart: unless-stopped
redis:
image: redis:alpine
container_name: redis
volumes:
- ./data/redis:/data
healthcheck:
test: [CMD-SHELL, 'redis-cli ping | grep PONG']
start_period: 20s
interval: 30s
retries: 5
timeout: 3s
networks:
- mx-space
restart: unless-stopped
networks:
mx-space:
driver: bridge
JWT_SECRETには16文字以上32文字以下の文字列を入力する必要がありますALLOWED_ORIGINSには許可するドメインを入力します。通常はフロントエンドのドメインです。複数のドメインを許可する場合は、半角カンマで区切ります。
その他は必要に応じて自分で変更できます。例えば '2333:2333' を '127.0.0.1:2333:2333' に変更するなど
これでCoreを起動できます
docker compose up -d
リバースプロキシ
1Panel や 宝塔 のようなモダンな管理パネルを使うことをお勧めします。非常に簡単です
新しいウェブサイトを作成し、リバースプロキシを設定して、URLアドレスを http://127.0.0.1:2333 にすれば完了です
Nginxを手動で設定したい場合は 公式Docs を参照してください
バックエンドの設定
ウェブサイトの設定画面で フロントエンドアドレス 管理画面アドレス API アドレス Gateway アドレス をそれぞれ以下のように設定します
- フロントエンドアドレス:
{Shiroアドレス} - 管理画面アドレス:
{バックエンドアドレス}/proxy/qaqdmin - API アドレス:
{バックエンドアドレス}/api/v2 - Gateway アドレス:
{バックエンドアドレス}
その他の設定は自分の判断で行ってください
Shiroのデプロイ
Mix Space管理画面に入り、「設定とクラウドファンクション」ページに移動し、右上の追加ボタンをクリックして、編集画面で以下の内容を入力します:
- 名称:
shiro - 参照:
theme - データタイプ:
JSON - データ:
以下の内容は自分のニーズに合わせて変更してください
{
"footer": {
"otherInfo": {
"date": "202x-{{now}}",
"icp": {
"text": "萌 ICP 備 xxxxxxxx 号",
"link": "https://icp.gov.moe/?keyword=xxxxxxxx"
}
},
"linkSections": [
{
"name": "関於",
"links": [
{
"name": "関於本站",
"href": "/about-site"
},
{
"name": "関於我",
"href": "/about"
},
{
"name": "関於此項目",
"href": "https://github.com/innei/Shiro",
"external": true
}
]
},
{
"name": "更多",
"links": [
{
"name": "時間線",
"href": "/timeline"
},
{
"name": "友連",
"href": "/friends"
},
{
"name": "監控",
"href": "https://status.xxxxx.xxx",
"external": true
}
]
},
{
"name": "聯繋",
"links": [
{
"name": "寫留言",
"href": "/message"
},
{
"name": "發郵件",
"href": "mailto:[email protected]",
"external": true
},
{
"name": "GitHub",
"href": "https://github.com/xxx",
"external": true
}
]
}
]
},
"config": {
"color": {
"light": [
"#33A6B8",
"#FF6666",
"#26A69A",
"#fb7287",
"#69a6cc",
"#F11A7B",
"#78C1F3",
"#FF6666",
"#7ACDF6"
],
"dark": [
"#F596AA",
"#A0A7D4",
"#ff7b7b",
"#99D8CF",
"#838BC6",
"#FFE5AD",
"#9BE8D8",
"#A1CCD1",
"#EAAEBA"
]
},
"bg": [
"/static/images/xxxxx.jpeg",
"/static/images/xxxxx.jpg"
],
"custom": {
"css": [],
"styles": [],
"js": [],
"scripts": []
},
"site": {
"favicon": "/xxxxx.svg",
"faviconDark": "/xxxxx-dark.svg"
},
"hero": {
"title": {
"template": [
{
"type": "h1",
"text": "xxxxxxx",
"class": "font-light text-4xl"
},
{
"type": "h1",
"text": "xxxxxxx",
"class": "font-medium mx-2 text-4xl"
},
{
"type": "h1",
"text": "👋。",
"class": "font-light text-4xl"
},
{
"type": "br"
},
{
"type": "h1",
"text": "xxxxxxx",
"class": "font-light text-4xl"
},
{
"type": "code",
"text": "<Developer />",
"class": "font-medium mx-2 text-3xl rounded p-1 bg-gray-200 dark:bg-gray-800/0 hover:dark:bg-gray-800/100 bg-opacity-0 hover:bg-opacity-100 transition-background duration-200"
},
{
"type": "span",
"class": "inline-block w-[1px] h-8 -bottom-2 relative bg-gray-800/80 dark:bg-gray-200/80 opacity-0 group-hover:opacity-100 transition-opacity duration-200 group-hover:animation-blink"
}
]
},
"description": "xxxxxxxxxxxxxxxxxxxx"
},
"module": {
"activity": {
"enable": true,
"endpoint": "/fn/ps/update"
},
"donate": {
"enable": true,
"link": "https://afdian.net/xxxxx",
"qrcode": [
"/static/images/xxxxxx.png",
"/static/images/xxxxxx.png"
]
},
"bilibili": {
"liveId": 1
}
}
}
}
Shiroフロントエンドのデプロイは、公式提供のビルド済み版と自前ビルド版に分けられます。必要に応じて選択してください
ビルド済み版は素早くサイトを立ち上げたい方に適していますが、深いカスタマイズはできません。繰り返しになりますが:必要に応じて選択してください
もう一点補足しておくと、スポンサー限定のクローズドソース版Shiroiを利用する場合は、公式がビルド済みイメージを提供していないため、自前ビルドを選択するしかありません
ビルド済み版
ビルド済み版は非常に手軽で、公式イメージを取得して環境変数を設定するだけで公開できます
mkdir shiro && cd shiro
nano docker-compose.yml
services:
shiro:
container_name: shiro
image: innei/shiro:latest
volumes:
- ./.env:/app/.env
- ./public:/app/public
restart: always
environment:
- NEXT_SHARP_PATH=/usr/local/lib/node_modules/sharp
ports:
- 2323:2323
環境変数を設定します
nano .env
以下を入力します
NEXT_PUBLIC_API_URL=https://{バックエンドアドレス}/api/v2
NEXT_PUBLIC_GATEWAY_URL=https://{バックエンドアドレス}
入力が完了したら docker compose up -d で起動できます
自前ビルド版
自前ビルドは少し手間がかかりますが、深いカスタマイズをしたい場合は唯一の選択肢です
ここでは GitHub Actions を使って Dockerイメージをビルドします。便利で手軽です
自分の GitHub にリポジトリを作成してください。プライベートリポジトリを推奨します。後で使います
お使いのPCに Git がインストールされていることを確認してください
適当なディレクトリでターミナルを開き、以下のコマンドを実行してください。注意:.git フォルダを削除しないでください。「リモートリポジトリのリネーム」方式を採用しているため、原作者が今後更新した際にも同期・マージが可能です
# 原作者のコードをクローン
git clone https://github.com/Innei/Shiro.git
cd Shiro
# 原作者のリポジトリを "upstream" にリネーム
git remote rename origin upstream
# 以下のURLを自分が作成したGitHubリポジトリのアドレスに置き換えてください
git remote add origin https://github.com/xxxxxx/xxxxx.git
ここからが重要です! .github / workflows フォルダに移動して中身を空にした後、docker-main.yml を新規作成します
そして以下の内容を記述します:
name: Docker (Main Branch)
on:
workflow_dispatch:
push:
branches:
- main
repository_dispatch:
types: [trigger-workflow]
permissions: write-all
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
PNPM_VERSION: 9.x.x
HASH_FILE: path/to/hash/file.txt
TZ: Asia/Shanghai
jobs:
prepare:
name: Prepare
runs-on: ubuntu-latest
if: ${{ github.event.head_commit.message != 'Update hash file' }}
outputs:
hash_content: ${{ steps.read_hash.outputs.hash_content }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Read HASH_FILE content
id: read_hash
run: |
content=$(cat ${{ env.HASH_FILE }}) || true
echo "hash_content=$content" >> "$GITHUB_OUTPUT"
check:
name: Check Should Rebuild
runs-on: ubuntu-latest
needs: prepare
outputs:
canceled: ${{ steps.use_content.outputs.canceled }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
lfs: true
- name: Use content from prev job and compare
id: use_content
env:
FILE_HASH: ${{ needs.prepare.outputs.hash_content }}
run: |
file_hash=$FILE_HASH
current_hash=$(git rev-parse --short HEAD)
echo "File Hash: $file_hash"
echo "Current Git Hash: $current_hash"
if [ "$file_hash" == "$current_hash" ]; then
echo "Hashes match. Stopping workflow."
echo "canceled=true" >> $GITHUB_OUTPUT
else
echo "Hashes do not match. Continuing workflow."
fi
build:
name: Build artifact
runs-on: ubuntu-latest
needs: check
if: ${{ needs.check.outputs.canceled != 'true' }}
outputs:
sha_short: ${{ steps.store.outputs.sha_short }}
branch: ${{ steps.store.outputs.branch }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
lfs: true
- name: Set up Image Name
run: |
IMAGE_ID=ghcr.io/${{ github.repository }}
IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]')
echo "IMAGE_ID=$IMAGE_ID" >> $GITHUB_ENV
- name: Login to Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Store artifact commit version
shell: bash
id: store
run: |
sha_short=$(git rev-parse --short HEAD)
branch_name=$(git rev-parse --abbrev-ref HEAD)
echo "sha_short=$sha_short" >> "$GITHUB_OUTPUT"
echo "branch=$branch_name" >> "$GITHUB_OUTPUT"
- name: Build Docker Image
run: |
docker build --build-arg TZ=Asia/Shanghai -t ${{ env.IMAGE_ID }}:${{ steps.store.outputs.sha_short }} .
- name: Push Docker Image to Github
run: |
docker push ${{ env.IMAGE_ID }}:${{ steps.store.outputs.sha_short }}
- name: Tag Docker Image as latest
run: |
docker tag ${{ env.IMAGE_ID }}:${{ steps.store.outputs.sha_short }} ${{ env.IMAGE_ID }}:latest
- name: Push Docker Image as latest
run: |
docker push ${{ env.IMAGE_ID }}:latest
保存して終了します
CMDウィンドウに戻ります
# ブランチ名がmainであることを確認
git branch -M main
# 最初のコミット(カスタマイズ可能)
git add .
git commit -m "Hello World"
# 自分のリポジトリにプッシュ
git push -u origin main
が完了したらGitHubリポジトリに戻って確認します

横にある小さな黄色い点をクリックして進行状況を確認できます



ビルド完了後、サーバー上で GitHubトークンを設定しないとイメージを取得できません
- GitHub →アバターをクリック → Settings → Developer Settings → Personal access tokens (classic) でトークンを作成します
- 有効期限は無期限に設定可能で、権限は
read:packagesのみで十分です - サーバーに移動して
echo "{あなたのToken}" | docker login ghcr.io -u {あなたのGitHub ID} --password-stdinを入力します
その後は手順通りに進めれば大丈夫です
mkdir shiro && cd shiro
nano docker-compose.yml
services:
shiro:
container_name: shiro
image: ghcr.io/{あなたのGitHub ID}/{あなたのリポジトリ名}:latest
volumes:
- ./.env:/app/.env
- ./public:/app/public
restart: always
environment:
- NEXT_SHARP_PATH=/usr/local/lib/node_modules/sharp
ports:
- 2323:2323
環境変数を設定します
nano .env
以下を入力します
NEXT_PUBLIC_API_URL=https://{バックエンドアドレス}/api/v2
NEXT_PUBLIC_GATEWAY_URL=https://{バックエンドアドレス}
入力が完了したら docker compose up -d で起動できます
これで一連の手順は完了です。あとはどう活用するかは皆さん次第です
コードの更新
原作者が継続的に更新する中で、最新の機能を同期したくなることがあるでしょう。先ほど Git の履歴を保持して upstream を設定しておいたおかげで、更新は非常に簡単です
コンフリクトがない場合は、たった3行のコマンドで更新が完了します
# 原作者の最新コードを取得
git fetch upstream
# ローカルにマージ
git merge upstream/main
# 自分のリポジトリにプッシュ
git push origin main
が発生した場合は、VS Codeで「調停」しましょう(笑
おわりに
サーバーが中国大陸にある場合、ghcr.ioイメージの取得が非常に遅くなったり失敗したりする可能性があります。この問題を解決するために、ビルド完了後にイメージをアリクラウド ACR に同期プッシュすることができます
docker-main.yml の env セクションに以下を追加します
# あなたのアリクラウドイメージリポジトリアドレス
ALIYUN_REGISTRY: crpi-xxxxxxxxx.cn-shanghai.personal.cr.aliyuncs.com
# あなたの名前空間
ALIYUN_NAMESPACE: xxxxx
# あなたのリポジトリ名
ALIYUN_REPO: xxxxx
そしてファイルの最後に以下を追加します
- name: Login to Aliyun ACR
uses: docker/login-action@v3
with:
registry: ${{ env.ALIYUN_REGISTRY }}
username: ${{ secrets.ALIYUN_USERNAME }}
password: ${{ secrets.ALIYUN_PASSWORD }}
- name: Tag & Push Image to Aliyun (Hash)
run: |
ALIYUN_IMAGE="${{ env.ALIYUN_REGISTRY }}/${{ env.ALIYUN_NAMESPACE }}/${{ env.ALIYUN_REPO }}:${{ steps.store.outputs.sha_short }}"
docker tag ${{ env.IMAGE_ID }}:${{ steps.store.outputs.sha_short }} $ALIYUN_IMAGE
docker push $ALIYUN_IMAGE
- name: Tag & Push Image to Aliyun (Latest)
run: |
ALIYUN_IMAGE_LATEST="${{ env.ALIYUN_REGISTRY }}/${{ env.ALIYUN_NAMESPACE }}/${{ env.ALIYUN_REPO }}:latest"
docker tag ${{ env.IMAGE_ID }}:latest $ALIYUN_IMAGE_LATEST
docker push $ALIYUN_IMAGE_LATEST
次に、アリクラウドのアカウントのフルネームとACRインスタンスの独立ログインパスワードを、リポジトリのActions Secretsの ALIYUN_USERNAME と ALIYUN_PASSWORD 変数にそれぞれ設定してください
設定が完了すると、次回コードをプッシュした際に、GitHub Actions が自動的にイメージをアリクラウドに同期します。これにより、国内サーバーでイメージの取得に30分もかかるということはなくなります