Back to Blog
Hosting

Self-Hosting Postiz on a Dedicated ThinkCentre M900 Tiny

Postiz took patience to self-host. A dedicated ThinkCentre M900 Tiny gives the service a clear boundary, with reliability verified through separate host-side checks.

Terry Hunt
August 24, 2026
7 min read

To self-host Postiz on a ThinkCentre M900 Tiny, I needed to plan for more than one application container. The documented stack uses PostgreSQL, Redis, Temporal, persistent storage, matching public URLs, and correctly registered social-provider callbacks. I chose a dedicated Lenovo ThinkCentre M900 Tiny so the service lifecycle stays separate from laptop shutdowns and unrelated homelab experiments. On August 23, 2026, the public hostname reached the Postiz authentication page through Cloudflare. That verifies public reachability, not container health, scheduled delivery, persistence, recovery, or uptime. I consider the setup dependable only when those host-side checks also pass.

Why Postiz became its own machine

I did not dedicate the M900 Tiny because Postiz needs exotic hardware. I did it because a scheduler is easier to reason about when its database, queues, uploads, and update cycle are not competing with unrelated experiments. A dedicated host keeps the service lifecycle separate from laptop shutdowns and unrelated lab-machine reboots. That architectural boundary does not by itself prove that scheduled posts have run reliably.

A tiny PC can be a practical middle ground for a personal workload, but the model name does not establish its installed CPU, memory, storage, power draw, purchase cost, or workload headroom. The documented fact here is that I chose a dedicated ThinkCentre M900 Tiny. It uses electricity and needs patches, storage checks, and backups. Self-hosting replaces a hosting bill with ownership; it does not remove operating work.

Understand the four-service Postiz stack

I start with the official Postiz Docker Compose guide because the required services have changed across releases. The official stack connects Postiz with PostgreSQL, Redis, and Temporal. PostgreSQL stores application data, Redis supports fast shared state, and Temporal handles background workflow execution. Current Postiz releases require Temporal, which is one reason an older self-hosting tutorial can fail even when its commands look reasonable.

Most early failures came from how those services addressed one another. Inside Docker, PostgreSQL uses its Compose service name rather than localhost. The browser-facing frontend and backend URLs use public addresses, while container-to-container requests use the internal backend address. I check each hostname, protocol, and port separately because healthy containers do not prove that a browser can complete the request.

How I expose it without exposing the host

The M900 Tiny is the dedicated Postiz machine, while Cloudflare sits in front of the public Postiz hostname. On August 23, 2026, I checked https://postiz.terryhunt.dev: it redirected to the Postiz authentication route and returned a successful page behind Cloudflare. That check proves the public entry point reached Postiz on that date. It does not prove every container, integration, or scheduled job is healthy.

Tailscale is the intended private administration path, while public users and social-provider callbacks use the Cloudflare hostname. Local status confirms relevant Linux peers are online, but it does not identify the Postiz peer or prove that SSH and firewall rules enforce this separation. I verify the host identity and exposure policy separately, and I avoid putting private IPs, machine names, or access tokens in screenshots and tutorials.

Match public, internal, and callback URLs exactly

Most of my trouble came from settings that looked plausible but were wrong. FRONTEND_URL must match the address the browser actually uses, including the protocol and port. The backend URL must be reachable from the browser, while the internal URL must make sense between containers. The Postiz self-hosting troubleshooting guide also warns that splitting the frontend and backend across incompatible parent domains can break OAuth state cookies.

The signing secret must be strong and persistent. Replacing JWT_SECRET during a restart logs users out and makes a normal redeploy look like an authentication problem. PostgreSQL data and uploaded media need persistent volumes. Social providers add their own developer apps, callback URLs, scopes, and approval rules. A correct Postiz deployment can still appear broken when one provider callback does not exactly match its registered value.

Use prebuilt images and persistent volumes

Postiz recommends its canonical Docker Compose repository and prebuilt image, and its documentation warns that a source build can run out of memory on a small server. The operating standard is to pin and review changes, pull the current Compose configuration, and read migration notes before replacing containers. I have not included host-side evidence of the M900's current image tag, Compose revision, or update procedure, so those remain items to verify before publication.

Container replacement should preserve the database, uploads, and secret values, but that requires verified mounts and stable secret configuration. The Postiz system requirements gives a supported floor and a higher recommendation for a small team. The model name does not reveal this M900's installed memory or storage, and the public login response does not prove its persistence boundaries.

My current ChatGPT and Codex handoff is manual

In the workflow documented here, ChatGPT and Codex prepare copy without receiving my Postiz credentials. I use ChatGPT to explore an angle or rewrite a message for different networks. I use Codex when the post needs facts from the site repository, an article draft, or a repeatable structured format. I check the claims, links, tone, and private details before moving the result into Postiz.

For a simple post, I paste the approved copy into Postiz and save it as a draft. That manual step is useful because it forces one last look at the selected account, media crop, link preview, and schedule. Different social networks have different limits and conventions, so one long source paragraph should not be pushed everywhere unchanged.

An optional API handoff I have not deployed

Postiz also exposes a public API. A self-hosted instance uses its own backend URL with the /public/v1 base path, and the API key comes from the developer settings described in the Postiz Public API introduction. The Postiz create-post endpoint accepts a post type such as draft, schedule, or now, along with content and the connected integration identifier.

A small local script could accept reviewed JSON from Codex and create a Postiz draft. OpenAI's OpenAI Responses API reference can generate text and request schema-constrained structured output. That makes the payload shape easier to validate; it does not validate the claims or make the Postiz handoff reliable by itself. I would keep the Postiz API key in the script environment, never in a chat prompt, and default every generated item to draft. Postiz would remain the calendar and final preview; a human would remain the publisher. This repository does not currently contain that connector.

The checks I run after setup and updates

A working login page is only the first check. My acceptance checklist confirms the application, PostgreSQL, Redis, and Temporal services are healthy. It creates a harmless draft, uses a test-capable social integration, previews its media and link, and schedules it far enough ahead to observe the workflow. It also restarts the host and verifies that secrets, database records, integrations, and uploads survive. This draft does not claim those host-side checks have passed; I need their dated results before publication.

Backups matter only if I know how to restore them. The database, uploads, Compose configuration, and secret inventory need a recovery plan that is stored away from the M900. Logs should be useful without containing post bodies, provider tokens, or session credentials. Those checks determine whether the scheduler can survive a restart and deliver a queued post.

Decide whether the ownership is worth it

Postiz can be a pain to self-host because it joins several moving parts with the strict callback rules of outside platforms. That frustration is concentrated at setup and upgrades. In return, I control the machine, the update timing, the content queue, and the path between my writing tools and the scheduler.

The dedicated ThinkCentre M900 Tiny makes that trade worthwhile for me because the boundary is obvious: this box runs Postiz. Someone who does not want to own Linux updates, backups, provider setup, and recovery may be happier with a managed service. For my homelab, that single-purpose boundary is the payoff I can support here; uptime and scheduling reliability still require host-side records.

Planning a small self-hosted publishing stack?

I can help map the containers, public URL, private access, backups, and review boundary before a social scheduler becomes another fragile box. Share the nonsecret parts of the plan through the contact page, or see my DevOps consulting services.

Related Articles

More articles in Hosting

Enjoyed This Article?

Get more tutorials and insights delivered to your inbox. No spam, just valuable content for web developers.