From Theme to Launch: The 8cat.life Build Log
This isn't a universal deployment tutorial, but a real record of taking 8cat.life from "I want to change my blog theme" to an actually accessible site. I've kept the detours we took so that when we rebuild, migrate, or troubleshoot later, we'll know why we made these choices.
What We Wanted to Do
The initial goal was simple: use the Yohaku theme and Mix Space Core to build a personal site for long-term writing.
Content isn't limited to any one technical direction. Here I'll write about code, AI, history, philosophy, and questions and ideas still taking shape.
The Final Setup
Here's the current structure:
The diagram omits two things we deliberately kept separate: content is always maintained in the Mix Space admin, while infrastructure and source code are managed via Git and CI. This way, writing a post doesn't require rebuilding the image; only frontend changes trigger the release pipeline.
Here's the current structure:
8cat.life: the Yohaku frontend, running in a Docker container on a Tencent Cloud Lighthouse server.api.8cat.life: Mix Space Core, reverse-proxied by Caddy to the Core service.- Core dependencies: PostgreSQL, Redis, and COS object storage.
- Frontend image: CNB builds the Docker image and pushes it to Tencent Cloud's private image registry, TCR.
- Auto-deployment: CNB uses Tencent Cloud TAT to call an automation agent on the Lighthouse server, which pulls the specified image and rebuilds the frontend container.
The key here is separating "building the image" from "running the image": CNB handles the build, and the server only pulls and runs the already-built version.
The Road Taken
1. Initially Tried Deploying Directly to EdgeOne Pages
Yohaku is a Next.js SSR project. EdgeOne Pages can recognize Next.js, but during the actual build, Next.js 16's Turbopack/webpack process was forcibly terminated by the platform's memory limit: the logs showed SIGKILL and a memory cap of about 6 GiB.
Even switching the build from Turbopack to webpack didn't help—the build was still killed. Additionally, EdgeOne's runtime had compatibility issues with the project's middleware/proxy. So we didn't continue hacking the production branch's routes to accommodate the platform.
Trade-off: EdgeOne is great for static sites and lightweight frameworks, but Yohaku's current SSR build is too heavy for that path. We kept the test branch and switched production to Docker.
2. GitHub Actions Can Build, but Wasn't Right as the Final Release Pipeline
GitHub Actions successfully built Yohaku, confirming that the source code, submodules, and Next.js build itself had no fundamental issues.
But after pushing the image to GHCR, pulling it from the Tencent Cloud server was slow due to cross-border latency. And having GitHub Runner SSH directly into the server for deployment would bring unfamiliar-IP alerts and long-term credential management headaches.
Trade-off: GitHub remains the primary source repository; image building and server deployment moved entirely into the Tencent Cloud ecosystem.
3. CNB, TCR, and TAT for Automated Deployment
We ended up using CNB for Docker builds, with artifacts pushed to a private TCR. Images are tagged with both an immutable commit SHA and latest; the server deploys the SHA tag, making it easy to trace "which commit is the site running." Deployment isn't about "recompiling code on the server"—it's about handing the server a verified, finished image.
This step had its share of pitfalls:
- When CNB used a domestic npm mirror, some packages weren't yet synced and returned 404; those packages needed to fall back to the official npm registry.
- The
git: not founderror during Docker builds came from install-phase hooks; the container build has no.gitdirectory, so it's harmless noise, not a build failure. - TAT initially used the wrong CVM-style instance ID; Lighthouse servers actually need the
lhins-...instance ID. - TAT CAM permissions need more than just
RunCommand; you also need permissions to query invocation and task status. Missing one of these results in "command sent but pipeline still fails."
Finally, after CNB finishes building, it calls TAT to have the server: pull the image for the specified SHA, update the compose service, and rebuild the yohaku container.
Content and Theme Configuration
In Mix Space, "pages" are ordinary CMS content, not Yohaku theme files. So the initial 404s for /about, /about-site, and /message in the footer were straightforward: those pages hadn't been created yet.
Now created:
- About me:
/about - About this site:
/about-site - Messages:
/message
Yohaku's homepage and footer visual configuration lives in the theme/shiro JSON under "Code Snippets" in the admin, not in the project root's theme.json. That's where you configure the homepage title, intro, hitokoto, favicon, and footer links.
Images, Favicon, and Email
- Images use Tencent Cloud COS. Core currently uses a single set of S3 connection settings, with blog post images and comment images isolated by different object path prefixes; the access key is granted only the minimal permissions needed for that bucket.
- The favicon was migrated from the old site; the frontend provides a 512px original for browsers to scale. When the old path is kept in the theme, the frontend gracefully falls back to this high-resolution resource.
- Email subscriptions use Resend. The sending domain is
updates.8cat.life, e.g., the sender addressnewsletter@updates.8cat.life. The API key is stored only in the Core admin configuration, never in the frontend or the code repository.
Limitations Still Worth Remembering
Google Comment Login
Google OAuth's browser authorization works, but the server also needs to reach oauth2.googleapis.com to exchange the authorization code for a token. The Core container on the Tencent Cloud server times out connecting to that address on port 443, so we end up with invalid_code.
This isn't a missing Google callback URL. To enable Google login, Core would need an HTTPS proxy that can reliably reach Google; for now, keeping the working GitHub login is the better choice.
RSS
The backend aggregation endpoint for RSS returns data correctly, but Yohaku's /feed route treated the optional RSS theme config as required, throwing an exception when the new theme config had no module.rss. The fix is to provide a fallback for the default value and add a regression test; don't mistake this for a Core, DNS, or subscription service issue.
Ongoing Optimizations
The production release pipeline is stable, but there are two follow-up tasks that don't affect writing:
- COS images could be connected to a CDN custom domain for more stable cross-region access and longer static caching; for now, we're using COS's official HTTPS domain to ensure correctness.
- The main site could also be connected to EdgeOne in the future, caching only static paths like
/_next/static/, fonts, and images; article content, admin, login, and API should still be dynamically served from origin, not sacrificed for superficial speed with whole-site caching.
Principles from This Build
- Don't make production source code unmaintainable just to fit a deployment platform.
- Build in CI; the server only runs immutable images.
- Secrets go only in the admin or CI secret variables, never in the Git repo or the frontend image layers.
- Every automated deployment uses a commit SHA tag, so issues can be traced back accurately.
- Admin content, theme configuration, and infrastructure configuration are three different layers; before troubleshooting, clarify which layer the problem belongs to.
Writing code is how I make a living, reading books is how I keep my spirit civilized, and thinking about questions is how I understand the world.