Skip to content

Repository files navigation

Postiz - Self-Hosted Social Media Scheduler (v0.4.0)

Replaces Buffer with Postiz, a self-hosted open-source social media scheduling tool. Deployed as a single UBI 10 systemd container.

RT #1392

Architecture

Single container running all services:

Service Port Purpose
PostgreSQL 16 5432 App DB + Temporal persistence + visibility
Redis 7 6379 Caching/sessions
Temporal 1.29.3 7233 Workflow orchestration (scheduled posts)
Node.js 22 (PM2) 3000, 4200 Backend + Frontend
nginx 5000 Internal reverse proxy

External: port 8092 -> container port 5000.

Build

podman build -t localhost/postiz:latest .

Build requires ~4GB memory for the Node.js compilation step.

Deploy

The live deployment configuration lives in the private host repo, not here.

Backups

postiz-backup.timer runs /usr/local/bin/postiz-backup.sh nightly at 04:00 container-local. It takes a pg_dumpall of the whole cluster — postiz, temporal, temporal_visibility and the temporal role — gzips it to a dated file under /root/.backups, keeps 14 days, and refreshes postiz-latest.sql.gz. The host bind-mounts that directory, and the weekly /srv sync ships it off-box.

pg_dumpall, not pg_dump: a postiz-only dump restores into a stack that will not start, because Temporal's schema and role come up missing.

The script refuses to publish a dump it cannot vouch for. It writes to a temp file, checks gzip integrity, and greps for three cluster markers before the file is allowed to replace yesterday's — pg_dumpall exits 0 on a connection it can open but has no rights to read, which yields a well-formed file containing nothing. A bad dump is worse than a missing one, because a missing one is visible.

Restore, into a scratch container:

su postgres -c "initdb -D /scratch/pg -A trust"
su postgres -c "pg_ctl -D /scratch/pg -o '-p 5433 -k /tmp' -w start"
gunzip -c postiz-latest.sql.gz | psql -p 5433 -h /tmp -U postgres -d postgres

Two errors are expected and harmless: current user cannot be dropped and role "postgres" already exists. --clean emits a DROP ROLE for the role running the restore, which can never succeed. Everything else must be silent.

This is a second layer, not the only one. The host's backup job takes its own independent pg_dumpall on the weekly and monthly rotations, and Nagios watches that copy for age and size. This timer exists to cut the worst-case RPO from seven days to one, and to keep dated history so a dump that goes bad cannot quietly overwrite the last good one. RT #1495.

Memory footprint

Upstream Postiz is built for multi-tenant SaaS. On a single-tenant install the defaults are badly oversized, and the cost lands mostly outside the V8 heap where --max-old-space-size cannot reach it. Three levers, in order of impact:

1. Temporal worker fan-out (~700MB). temporal.module.ts registers one worker per supported provider, not per connected one — 31 workers, each with its own @temporalio/core-bridge Rust core and thread pair. Set POSTIZ_ACTIVE_PROVIDERS to the providers you actually use. Confirmed by thread accounting: 31 workflow-proces + 31 temporal-real-s threads, exactly one pair per worker.

2. Wrapper processes (~195MB). Upstream launches each app as pm2 -> pnpm start -> dotenv -> node, costing two extra Node processes per app. dotenv -e ../../.env reads a file this image never creates, so it is pure overhead. ecosystem.config.js invokes node directly instead.

3. Heap and pool sizing. Per-app --max-old-space-size via PM2 interpreter_args (a CLI flag beats an inherited NODE_OPTIONS; the resulting V8 ceiling is the flag + ~48MB). Temporal datastore pools and postgres max_connections trimmed to match single-tenant reality.

Note that maxCachedWorkflows defaults to a value derived from v8.getHeapStatistics().heap_size_limit, so the heap cap silently bounds the sticky workflow cache too. We set it explicitly rather than relying on that side effect.

MCP Integration

Postiz exposes a Public API for scheduling posts. Due to SSE transport issues behind nginx, use the REST API approach:

  • Base URL: https://postiz.crunchtools.com/api/public/v1
  • Auth: API key from Postiz Settings UI in Authorization header
  • Endpoints: GET /integrations, POST /posts, POST /upload

Fallback: Multi-Stage from Official Image

If building from source on UBI fails, replace the build section in the Containerfile:

# Replace the git clone + pnpm install + pnpm build steps with:
FROM ghcr.io/gitroomhq/postiz-app:v2.19.0 AS postiz-source
# Then in the final stage:
COPY --from=postiz-source /app /app
RUN cd /app && npm rebuild

About

Self-hosted Postiz social media scheduler on UBI 10

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages