Mix Space is a modern personal space backend built on a front-end/back-end separation architecture; Shiro is its official companion high-aesthetic front-end theme, built on Next.js, renowned for its minimalist design and smooth animations. When used together, they provide you with a personal blog system that integrates article writing and daily updates, while delivering exceptional performance and visual appeal. — Gemini
I previously used Hexo, but struggled with difficult updates and limited functionality (because I prefer self-hosting and don't really like PaaS). Even updating a single character required rebuilding and re-uploading to the server, making fragmented writing very difficult.
Mix Space and Shiro complement each other, serving as the backend (Core) and frontend (Theme) respectively. There are also other frontend themes available (such as Kami and Yun). This article only covers Mix Space (backend) and Shiro (frontend).
For backend deployment, you can directly use the official pre-built Docker image. For the Shiro frontend, you can use either a pre-built Docker image or a self-built Docker image. Self-building allows deep modifications to the frontend pages (if you need to display two or more ICP filing numbers, self-building is mandatory).
Enough talk, let's get started!
Backend deployment can be done simply by pulling the image with Docker Compose
Then write the following in docker-compose.yml:
JWT_SECRET must be a string with a length of no less than 16 characters and no more than 32 charactersALLOWED_ORIGINS should contain the allowed domains, typically the frontend domain. If multiple domains are allowed, separate them with commas.You can modify other settings as needed, for example changing '2333:2333' to '127.0.0.1:2333:2333'
Then you can start Core
It's recommended to use modern panels like 1Panel or BT Panel for this, as it's very straightforward
Create a new website, configure the reverse proxy, set the URL to http://127.0.0.1:2333 and you're done
If you want to manually configure Nginx, refer to the official docs
Go to the website settings and set the Frontend URL, Admin Dashboard URL, API URL, and Gateway URL as follows:
{Shiro URL}{Backend URL}/proxy/qaqdmin{Backend URL}/api/v2{Backend URL}For everything else, just configure as you see fit
Go to the Mix Space admin panel, navigate to the "Configurations & Cloud Functions" page, click the add button in the top right corner, and fill in the following on the edit page:
shirothemeJSONModify the following content according to your needs
Shiro frontend deployment can be divided into the official pre-built version and the self-built version. Choose according to your needs.
Pre-built is suitable for quickly setting up a site, but doesn't allow deep modifications. As mentioned: choose according to your needs.
One more thing — if you've sponsored the closed-source version Shiroi, you can only choose self-building, as the official team doesn't provide a pre-built image for it.
Pre-built is very quick — just pull the official image, configure the environment variables, and you're ready to go live
Configure environment variables
Then fill in
Once filled in, you can run docker compose up -d
Self-building is a bit more involved, but if you want deep customization, this is your only option
Here we use GitHub Actions to build the Docker image — convenient and fast
Go to your GitHub and create a repository. A private repository is recommended, as we'll need it shortly
Make sure you have Git installed on your computer
Open a terminal in a directory and run the following commands. Note: do not delete the .git folder. We use a "rename remote" approach so that you can sync and merge when the original author pushes updates later
Here's the key part! Navigate to and clear the .github / workflows folder, then create a new file docker-main.yml
And fill in the following:
Save and exit
Then return to the CMD window
After pushing, go back to your GitHub repository to check

You can click the small yellow dot next to it to view the progress



After the build is complete, you still need to configure a GitHub Token on your server to pull the image
read:packagesecho "{your-token}" | docker login ghcr.io -u {your-GitHub-ID} --password-stdinThen follow the process as usual
Configure environment variables
Then fill in
Once filled in, you can run docker compose up -d
Alright, the entire process is now complete. How you want to tinker with it from here is up to you.
As the original author continues to update, you may want to sync the latest features. Thanks to the fact that we preserved the Git history and configured upstream earlier, updating becomes very simple.
If there are no conflicts, you only need three commands to complete the update
If there are conflicts, just head to VS Code to "mediate" them.
If your server is located in mainland China, pulling images from ghcr.io may be very slow or even fail. To solve this problem, you can sync and push the image to Alibaba Cloud ACR after the build is complete.
Add the following to the env section in docker-main.yml
Then add the following at the end of the file
Next, configure your Alibaba Cloud account full name and the ACR instance's standalone login password as the ALIYUN_USERNAME and ALIYUN_PASSWORD variables in the repository's Actions Secrets respectively.
Once configured, the next time you push code, GitHub Actions will automatically sync the image to Alibaba Cloud. This way, servers in China will never have to spend half an hour pulling images again.
cd && mkdir -p mx-space/core && cd $_
nano 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
docker compose up -d
{
"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
}
}
}
}
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://{backend-url}/api/v2
NEXT_PUBLIC_GATEWAY_URL=https://{backend-url}
# Clone the original author's code
git clone https://github.com/Innei/Shiro.git
cd Shiro
# Rename the original author's remote to "upstream"
git remote rename origin upstream
# Replace the URL below with your own GitHub repository URL
git remote add origin https://github.com/xxxxxx/xxxxx.git
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
# Make sure the branch name is main
git branch -M main
# Commit the first change (you can customize the message)
git add .
git commit -m "Hello World"
# Push to your repository
git push -u origin main
mkdir shiro && cd shiro
nano docker-compose.yml
services:
shiro:
container_name: shiro
image: ghcr.io/{your-GitHub-ID}/{your-repo-name}: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://{backend-url}/api/v2
NEXT_PUBLIC_GATEWAY_URL=https://{backend-url}
# Fetch the latest code from the original author
git fetch upstream
# Merge into your local branch
git merge upstream/main
# Push to your own repository
git push origin main
# Your Alibaba Cloud container registry address
ALIYUN_REGISTRY: crpi-xxxxxxxxx.cn-shanghai.personal.cr.aliyuncs.com
# Your namespace
ALIYUN_NAMESPACE: xxxxx
# Your repository name
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