Skip to content

RE-ENABLED 2026-09-17 at Angel's request to finish the Meta connection. All six services remain behind compose profile social; the data volumes and public API configuration were preserved. Postiz is healthy at social.embeddedera.com. Available memory must be watched because the stack previously held about 3 GB idle. type: object cluster: containers universe: live status: verified verified: 2026-09-19 entity: /home/angel/torrentvpn/docker-compose.yml#postiz


postiz — social.embeddedera.com (social post scheduler)

PAUSED 2026-10-16 (Facebook/Instagram) until the owner says resume. Flag ~/.config/social-paused; resume steps in content-pipeline "Social pause". Website notes keep publishing.

ghcr.io/gitroomhq/postiz-app:latest, service postiz near the end of the torrentvpn compose file, bound to 127.0.0.1:4007 only; public through tunnel plex behind the Access app "Social scheduler" (token social in departments.yml, granted to admin and sales).

Shape

  • postiz — the app itself (frontend + API on internal port 5000, published as 4007)
  • postiz-postgres (postgres:17-alpine) — its own database, not shared with any other service
  • postiz-redis (redis:7.2) — queues/cache
  • postiz-temporal + postiz-temporal-postgres (postgres:16) + postiz-temporal-es (elasticsearch:7.17.27) — Temporal workflow engine, required by the current upstream compose (docs.postiz.com/installation/docker-compose, mirrored at github.com/gitroomhq/postiz-docker-compose) even for a single-tenant install. This is the heaviest part of the stack (an Elasticsearch JVM); if Postiz ever drops the Temporal requirement, all three postiz-temporal* services can go.

None of the six services publish a port except postiz itself (loopback only) — same shape as inventree-db/inventree-cache/inventree-server.

Scheduler health and recovery

The container health check requires both the public web process on port 5000 and the internal orchestrator health endpoint on port 3002. postiz-scheduler-watchdog.timer independently checks port 3002 every five minutes. After a three-minute startup grace and a second failed probe, it restarts only the in-container orchestrator, waits up to three minutes for recovery, records state in ~/.local/state/assistant/postiz-scheduler-watchdog.json, and sends ntfy on recovery or failure. It does not create, retry, edit or delete posts.

Daily calendar gaps are checked separately by content-schedule-audit.timer every 15 minutes, after the existing delivery observation fetch. It compares the two feed destinations for today and the next seven Eastern calendar dates against current provider observations and dated imported publications. Reports live in ~/.local/state/assistant/social-coverage.json and Home Content; ntfy warns on changes and repeats unresolved issues every six hours. The September 21 feed gap was an empty calendar slot, not a stalled worker: old feed publications must not satisfy new-day coverage. Recovery and controls are recorded in content-pipeline and SOP-018 v6.0.

This exists because a full Docker daemon restart on 2026-09-19 restored the Postiz web interface but left the orchestrator stalled before it registered Temporal pollers. The old web-only health check remained green, and the 18:00 Instagram feed and Story waited in Temporal until the worker was restarted at 19:26. Both original jobs then published without replacements. Source: assistant/scripts/postiz-scheduler-watchdog.py; units are tracked under assistant/systemd/.

Config

  • postiz/postiz.env (0600) — JWT_SECRET and the Postgres password are random, generated at install; MAIN_URL / FRONTEND_URL / NEXT_PUBLIC_BACKEND_URL all point at social.embeddedera.com; DISABLE_REGISTRATION=false for the first login (flip to true once the account exists — this app has no Cloudflare Access-driven auto-provisioning like Grafana, so it is its own login surface); API_LIMIT=90; NOT_SECURED=false.
  • Every social-provider key (X_API_KEY, LINKEDIN_CLIENT_ID, FACEBOOK_APP_ID, …) ships blank with a comment — creating the provider apps and filling these in is Angel's job, not something to automate here (each is a developer-portal registration tied to a real brand identity).
  • postiz/dynamicconfig/development-sql.yaml — Temporal's dynamic config file, pulled from the upstream repo; without it postiz-temporal crash-loops (Unable to create dynamic config client).
  • Volumes: postiz/postgres, postiz/redis, postiz/temporal-postgres, postiz/temporal-es, postiz/postiz-config, postiz/postiz-uploads — all bind dirs under ~/torrentvpn/postiz/, same convention as every other service's config dir.

Access

Cloudflare Access app "Social scheduler", created by onboard access-app --name "Social scheduler" --domain social.embeddedera.com --token social --yes (SOP-019: Access apps are never hand-built). onboard access-sync keeps its allow-list in sync with departments.yml.

Reachable since 2026-09-15: cf-host add social.embeddedera.com http://127.0.0.1:4007 --yes added the tunnel ingress and the proxied CNAME; https://social.embeddedera.com/ answers 302 (the Access login).

Public API

Once someone has logged in, Settings → Public API gives a personal API key for POST https://social.embeddedera.com/public/v1/posts (and friends) — the automation surface for scheduling posts from a script instead of the UI. Rate-limited to API_LIMIT=90/hour, shared across every caller using that key.

If you change this

  • Hits: the tunnel plex ingress list, departments.yml (social token, admin/sales tiles), ~/assistant-sandbox/home.py TILES (sandbox branch — not yet merged/deployed to ~/assistant/home.py), surfaces-check PAGES.
  • Does not hit: any other container — Postiz's Postgres/Redis/Temporal are entirely its own.

See

  • Source: /home/angel/torrentvpn/docker-compose.yml · ~/torrentvpn/postiz/postiz.env
  • Docs: https://docs.postiz.com/installation/docker-compose · https://github.com/gitroomhq/postiz-docker-compose
  • Model: grafana (the other Access-gated, loopback-only service card)

Login (2026-09-15): Cloudflare Access as the OIDC provider does NOT work — Postiz's generic OAuth sends no state and Access refuses (state is a required parameter). Access SaaS app "Postiz login" exists but is unused. Plan: a Google OAuth client (Workspace, internal) as the generic provider instead; until then email + password on the local account.

Pipeline (2026-09-15): publish-note approve → note-to-social <slug> (LinkedIn post, X thread, Instagram caption + carousel cards, video script) → social-push <slug> --yes (drafts in Postiz via /api/public/v1/posts, one per connected account, at the content plan's publishing times; idempotent through ~/embeddedera-content/<slug>/postiz.json). Until accounts are connected it prints 'no accounts connected' and exits 0. Login: the built-in Google provider (YOUTUBE_CLIENT_ID/SECRET), registration off; Yara gets in by invitation from Settings → Team.

Meta app setup (2026-09-17): app name EmbeddedEra Social Publisher; company/business portfolio EmbeddedEra LLC; contact hello@embeddedera.com; app domain embeddedera.com; privacy/data-deletion instructions https://embeddedera.com/privacy/. The Facebook Login valid OAuth redirect URIs used by the current Postiz source are https://social.embeddedera.com/integrations/social/facebook and https://social.embeddedera.com/integrations/social/instagram. Use the Facebook Business path for Instagram because the company Instagram account must be professional and linked to the company Facebook Page. Store the resulting app ID and secret only in postiz/postiz.env as FACEBOOK_APP_ID and FACEBOOK_APP_SECRET, then recreate only the postiz service. The standalone Instagram path is separate and uses INSTAGRAM_APP_ID/INSTAGRAM_APP_SECRET with callback /integrations/social/instagram-standalone; it is not the selected path.

After Meta creates the credentials, run postiz-meta-config. It prompts for the App ID and a hidden App Secret, updates the 0600 environment file atomically, recreates only the Postiz app service and waits for its local health check. Do not paste the secret into chat, notes, Kanboard or a shell command.

Meta connection verified 2026-09-17: Facebook embeddederaofficial (EmbeddedEra Page 254051434466101) and Instagram embedded_era (17841465671705929) are active Postiz channels. Instagram displays the owner’s personal name but its profile/ID matches the business account. Installed version v1.47.0 supports Facebook and Instagram post_type: post|story. The prior Graph check using the Facebook token did not establish that the accounts were disconnected; Meta Business Settings confirmed the link. Do not remove/relink accounts based solely on an empty token-scoped Graph response.

Approved artwork access (2026-09-17): Cloudflare Access app Approved social artwork (public) bypasses only social.embeddedera.com/uploads/approved/*. The app root and ordinary uploads remain gated. Approved artwork is copied by the scheduler only after explicit package approval to /home/angel/torrentvpn/postiz/postiz-uploads/approved/<sha256>.png. Provisioner: assistant/scripts/postiz-public-media.py (dry run by default). No customer/intake files belong in this directory. Verified nonexistent approved image returns origin 404 while root redirects to Access; onboard access-sync reports governed apps in sync.

  • 2026-09-22: Owner split Story publishing into a separate workflow. Eight queued legacy Story posts (September 23–26) deleted through the supported public API after verifying each group contained only that Story; all eight Temporal workflows terminated. All existing feed records unchanged. WebUSB feed pair scheduled September 23 09:00/18:00 Eastern. Current feed pipeline never schedules Stories, including old-approval retries; details in content-pipeline.