I wanted a personal publishing platform that would let me write a post once, have it automatically translated, share it on LinkedIn, and schedule the publication – all without juggling multiple tools or a heavyweight CMS. The solution had to run on my own Kubernetes node, be fast for visitors, and keep the editorial workflow friction‑less.
| Component | Choice & Rationale |
|---|---|
| Frontend / rendering | SvelteKit with Server‑Side Rendering (SSR). Because most pages are static, SSR delivers sub‑second first‑byte times without a separate build step. |
| Database | PostgreSQL – stores markdown content, inline images/PDFs (as BLOBs), version metadata and LinkedIn post settings. |
| Automation / translation | n8n workflows triggered from the UI. A post is sent to n8n, which calls a translation service, stores the localized versions, and queues a LinkedIn payload. |
| Containerisation | Docker images built per commit; the image includes the SvelteKit app and the n8n runner. |
| Orchestration | Kubernetes with Kustomize overlays for dev / prod environments. The cluster is the same one I already operate for other side‑projects (see my personal cloud platform) . |
| CI/CD | GitHub Actions + release‑it. A push to main runs lint, unit tests, creates a new semantic version, builds the Docker image and updates the Kustomize kustomization.yaml. Argo CD (or a kubectl apply) then rolls the new release to the cluster. |
| Versioning | Within the app each article has a version field (draft → review → published). The overall app version is bumped automatically by release‑it during the CI run. |
| Scheduling | A cron‑job inside the cluster calls the n8n endpoint every 48 h, publishing any queued LinkedIn posts. |
| Image/PDF handling | Uploaded files are streamed directly into PostgreSQL, avoiding an external object store and keeping the deployment truly self‑hosted. |
| Trivia / image scanning | A small Go service (outside the scope of the blog) watches the uploaded assets and runs a quick scan for prohibited content – a proof‑of‑concept for future moderation. |
/api/translate. n8n fetches the markdown, sends it to a translation API, and writes back the localized copies. CronJob triggers the n8n “post to LinkedIn” workflow, pulling any ready payloads and sending them to LinkedIn’s API.All steps happen with a single button click in the admin UI; no manual copy‑pasting or external script execution is required.
kubectl rollout) performs a rolling update. My existing GitOps workflow on the same cluster cuts deployment time from hours to minutes. | Challenge | Solution |
|---|---|
| Concurrent edits – Two editors could modify the same article. | Added an optimistic‑locking version field; the UI warns the user if the DB version changed since they loaded the draft. |
| Large media uploads – Storing PDFs in PostgreSQL could bloat the DB. | Implemented streaming inserts and set a 5 MB size limit per file; larger assets are rejected with a clear UI message. |
| Translation latency – External translation APIs can be slow. | n8n runs the translation asynchronously; the UI displays a “translation in progress” badge and updates automatically when the job finishes. |