<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Aryan Iyappan</title>
    <description>The latest articles on DEV Community by Aryan Iyappan (@aryaniyaps).</description>
    <link>https://dev.to/aryaniyaps</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F664689%2Ffe45d2e0-7876-4c54-8e03-3b77a771fb58.png</url>
      <title>DEV Community: Aryan Iyappan</title>
      <link>https://dev.to/aryaniyaps</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aryaniyaps"/>
    <language>en</language>
    <item>
      <title>96.2% vs 6.9% — I Watched 5 Frontier LLMs Fail at Sudoku While an Energy-Based Model Solved It in 0.24s</title>
      <dc:creator>Aryan Iyappan</dc:creator>
      <pubDate>Tue, 09 Jun 2026 14:00:01 +0000</pubDate>
      <link>https://dev.to/aryaniyaps/962-vs-69-i-watched-5-frontier-llms-fail-at-sudoku-while-an-energy-based-model-solved-it-in-c77</link>
      <guid>https://dev.to/aryaniyaps/962-vs-69-i-watched-5-frontier-llms-fail-at-sudoku-while-an-energy-based-model-solved-it-in-c77</guid>
      <description>&lt;h1&gt;
  
  
  I Watched 5 Frontier LLMs Fail at Sudoku While an Energy-Based Model Solved It in 0.24 Seconds
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Yann LeCun chairs the technical board. The live demo makes his case better than any paper ever could — and the architecture implications go far deeper than puzzles.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;Last week I spent an hour on a website that made me question everything I thought I knew about AI reasoning. It wasn't a research paper. It wasn't a benchmark leaderboard. It was a Sudoku solver.&lt;/p&gt;

&lt;p&gt;I loaded a hard puzzle and clicked "Compare All Models." Six AI systems raced to solve it simultaneously.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2p83o492jyrzkcnwhfn3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2p83o492jyrzkcnwhfn3.png" alt="KONA EBM vs LLMs comparison — EBM solved correctly in 0.24s while LLMs are still running or producing errors" width="800" height="772"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;KONA 1.0 (EBM)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Correct — valid grid&lt;/td&gt;
&lt;td&gt;0.24s&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM 1&lt;/td&gt;
&lt;td&gt;⏳ Timed out — never finished&lt;/td&gt;
&lt;td&gt;20s+&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM 2&lt;/td&gt;
&lt;td&gt;⏳ Timed out — lost in reasoning&lt;/td&gt;
&lt;td&gt;50s+&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM 3&lt;/td&gt;
&lt;td&gt;⏳ Timed out — reasoning diverged&lt;/td&gt;
&lt;td&gt;50s+&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM 4 (Gemini 3 Pro)&lt;/td&gt;
&lt;td&gt;❌ API Error — returned 404&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM 5&lt;/td&gt;
&lt;td&gt;❌ Wrong — 14 duplicate digits&lt;/td&gt;
&lt;td&gt;2.93s&lt;/td&gt;
&lt;td&gt;$0.0005&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The LLMs didn't just lose. They failed in ways that reveal structural limitations in how every frontier model today approaches reasoning.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqybb0ymbjngd2gi7xh0v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqybb0ymbjngd2gi7xh0v.png" alt="LLM 2 and LLM 3 stuck in infinite chain-of-thought. LLM 4 errored out. LLM 5 wrong with 14 duplicates" width="800" height="772"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;LLMs 2 and 3 spent their entire runtime producing verbose chain-of-thought. &lt;em&gt;"Let me analyze this row by row, column by column. Column 0 has 6, 9, 2, 5 filled, leaving 1, 3, 4, 7, 8. Box 3 in the middle-left has..."&lt;/em&gt; They generated hundreds of tokens of reasoning that looked correct at every step — identifying the right constraints, analyzing the right cells, considering the right candidates. And they never converged. The reasoning drifted. The constraints multiplied. There was no mechanism to notice that the chain of thought had become incoherent.&lt;/p&gt;

&lt;p&gt;LLM 5 finished. It output a complete 9×9 grid with all cells filled and numbers in plausible positions. It had 14 duplicate digits across rows, columns, and boxes. The model produced output that was locally coherent — each token followed reasonably from the previous token — but globally broken.&lt;/p&gt;

&lt;p&gt;The demo is at &lt;a href="https://sudoku.logicalintelligence.com" rel="noopener noreferrer"&gt;sudoku.logicalintelligence.com&lt;/a&gt;. Test it yourself before reading further — the experience of watching frontier models fail in real time while an unknown architecture succeeds changes how you think about AI reasoning.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture Problem Nobody Is Talking About
&lt;/h2&gt;

&lt;p&gt;The company behind this is &lt;a href="https://logicalintelligence.com" rel="noopener noreferrer"&gt;Logical Intelligence&lt;/a&gt;. Their product: Energy-Based Reasoning Models. Their flagship model: KONA. Their orchestrator: Aleph. And the name on their Technical Research Board is &lt;strong&gt;Yann LeCun&lt;/strong&gt; — Founding Chair.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpub-25e7bed4d05d4eae8fa8e3ef0648a9cd.r2.dev%2FDiZYzB7HOG.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpub-25e7bed4d05d4eae8fa8e3ef0648a9cd.r2.dev%2FDiZYzB7HOG.png" alt="Logical Intelligence homepage — " width="800" height="2281"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The team is serious. Michael Freedman — Fields Medalist, one of the most decorated mathematicians alive — is Chief Science Officer. Eve Bodnia is Founder and CEO. Boris Hanin (Princeton ORFE) is a technical advisor. This is not a side project.&lt;/p&gt;

&lt;p&gt;LeCun has spent over a decade arguing that autoregressive LLMs cannot reach human-level reasoning. Most of the industry tuned him out because LLMs kept getting better at benchmarks. But this demo exposes exactly the limitations he's been warning about — and it's live, public, and testable by anyone with a browser.&lt;/p&gt;

&lt;p&gt;To understand why the Sudoku demo is so revealing, you need to understand the structural difference between how LLMs and Energy-Based Models approach reasoning.&lt;/p&gt;

&lt;h3&gt;
  
  
  How LLMs Reason — And Why They Hit a Wall
&lt;/h3&gt;

&lt;p&gt;Every frontier model today — GPT-5.2, Claude 4, Gemini 3, DeepSeek V3.2 — shares the same fundamental architecture: &lt;strong&gt;autoregressive generation&lt;/strong&gt;. They produce tokens one at a time, left to right. Each token is a hard commitment. There's no native mechanism to go back and revise an earlier token when a later constraint invalidates it.&lt;/p&gt;

&lt;p&gt;When an LLM "reasons," it produces a chain of thought — a sequence of tokens walking through the problem step by step. This works well for language because natural language is forgiving. Small inconsistencies don't break meaning. You can say something slightly contradictory and still be understood.&lt;/p&gt;

&lt;p&gt;But reasoning has three structural problems when you try to do it autoregressively:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. No undo.&lt;/strong&gt; If step 7 contradicts step 2, you can't fix step 2. You'd need to regenerate every token from step 2 onward. In practice, the model doesn't even detect the contradiction — it keeps generating tokens that drift further from global consistency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Locally scored.&lt;/strong&gt; LLMs are trained to predict the next token given the previous ones. This optimizes for local coherence, not global correctness. A reasoning chain can be perfectly coherent at every step and still reach a fundamentally wrong conclusion. The training signal has no concept of "the final answer must satisfy all 27 constraints simultaneously."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Discrete token space.&lt;/strong&gt; Reasoning traces are sequences of discrete tokens. You can't make small, gradient-based edits to improve consistency. Improvement requires discrete search, reranking, or resampling — all of which are expensive, lossy, and unreliable for long chains.&lt;/p&gt;

&lt;p&gt;Here's exactly how an LLM approaches a constraint satisfaction problem:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9jwirnazbi639ff0aadx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9jwirnazbi639ff0aadx.png" alt="Carbon: LLM autoregressive approach — token by token, commit without undo, no global consistency check" width="799" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Sudoku demo demonstrates all three failures simultaneously. The LLMs produced reasoning that looked correct at each step. They identified the right constraints. They analyzed the right cells. They considered the right candidates. But they couldn't maintain global consistency across 81 cells and 27 constraints. The chain of thought drifted. Errors accumulated silently. There was no mechanism to notice, locate, or correct them.&lt;/p&gt;

&lt;p&gt;This is not a "bad prompt" problem. It's not a "needs more training" problem. It's an architecture problem. The model was never designed to verify global consistency because its fundamental operation — predict the next token — has no concept of "the whole."&lt;/p&gt;

&lt;h3&gt;
  
  
  How Energy-Based Models Reason — And Why It's Different
&lt;/h3&gt;

&lt;p&gt;EBMs work on a fundamentally different principle. Instead of generating tokens left to right, they learn an &lt;strong&gt;energy function&lt;/strong&gt; — a scalar score that evaluates how consistent any state is with all constraints simultaneously.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Low energy = valid. High energy = something is broken.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The crucial property that makes this architecture categorically different: the energy function works on &lt;strong&gt;partial solutions&lt;/strong&gt;. You can evaluate a half-completed grid and get actionable feedback on which specific constraints are being violated, and where the violation was introduced. This turns reasoning from a sampling problem (generate tokens, hope they're correct) into an optimization problem (minimize energy by satisfying constraints).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F39pltp8vzk1w4cqo7nad.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F39pltp8vzk1w4cqo7nad.png" alt="Carbon: Energy-Based Model approach — evaluate globally, gradient descent toward consistency, guaranteed valid output" width="800" height="332"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Logical Intelligence's three core theses — stated directly in their &lt;a href="https://logicalintelligence.com/blog/energy-based-models-for-reasoning" rel="noopener noreferrer"&gt;technical blog&lt;/a&gt; — are worth reading in full:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Thesis 1:&lt;/strong&gt; LLMs are fundamentally limited as reasoning models due to their reliance on discrete tokens generated left-to-right. This is a serious impediment for scaling up AI reasoning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thesis 2:&lt;/strong&gt; Energy-Based Reasoning Models overcome the main difficulties inherent in using LLM-based reasoning models. The key advantage: EBRMs provide a score you can apply to intermediate states that tells you whether you're staying consistent with global constraints and helps you pinpoint what is broken so you can repair it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thesis 3:&lt;/strong&gt; Scaling AI reasoning requires using EBRMs for reasoning and LLMs for coordination, especially when translating to and from natural language instruction.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;KONA, their flagship model, embodies all three theses. It is &lt;strong&gt;non-autoregressive at the trace level&lt;/strong&gt; — it generates complete reasoning traces simultaneously and conditions directly on the problem constraints. It reasons in a &lt;strong&gt;continuous latent space&lt;/strong&gt; using dense vector tokens rather than discrete ones, which enables gradient-based refinement. It is &lt;strong&gt;globally scored&lt;/strong&gt; — the energy function evaluates end-to-end trace quality directly, so long-horizon coherence is trained and optimized natively.&lt;/p&gt;

&lt;p&gt;The performance gap isn't subtle. On their benchmark of hard Sudoku puzzles:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Accuracy&lt;/th&gt;
&lt;th&gt;Avg Latency&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;KONA 1.0 (EBM)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;96.2%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;313 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-5.2&lt;/td&gt;
&lt;td&gt;6.9%&lt;/td&gt;
&lt;td&gt;varies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude models&lt;/td&gt;
&lt;td&gt;"close but still wrong"&lt;/td&gt;
&lt;td&gt;"think for a long time"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DeepSeek V3.2&lt;/td&gt;
&lt;td&gt;"fast but many duplicates"&lt;/td&gt;
&lt;td&gt;"finishes quickly"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;From their published benchmark results: "Claude models often think for a long time and then produce something close but still wrong. GPT-5.2 does best among the LLMs at 6.9%, which is still a 93% failure rate on a puzzle that any patient human can solve. DeepSeek V3.2 tends to finish quickly but with many duplicates across rows, columns, and boxes — failing to solve the puzzle."&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bigger Picture: Aleph and PutnamBench
&lt;/h2&gt;

&lt;p&gt;This isn't just about Sudoku. Logical Intelligence's orchestrator &lt;strong&gt;Aleph&lt;/strong&gt; coordinates KONA, LLMs, and other tools in a compound system. Aleph recently achieved a near-perfect score on &lt;a href="https://logicalintelligence.com/blog/aleph-solves-putnambench" rel="noopener noreferrer"&gt;PutnamBench&lt;/a&gt; — a formal reasoning benchmark based on the William Lowell Putnam Mathematical Competition, widely considered the most prestigious university-level mathematics competition in North America.&lt;/p&gt;

&lt;p&gt;These aren't grid puzzles. These are problems that most math PhDs cannot solve — problems where correctness is formally verifiable, meaning you cannot fake it with plausible-sounding output. The reasoning must be logically sound or it doesn't count at all.&lt;/p&gt;

&lt;p&gt;Aleph has also topped other formal reasoning benchmarks, including MiniF2F and ProofNet. And in a separate result, Aleph formalized and disproved an open problem in planar unit distance graph theory — a problem in the Erdős unit distance problem family that had resisted resolution for years. The team published the formal Lean 4 proof alongside the result.&lt;/p&gt;

&lt;p&gt;This is not an LLM getting lucky on a benchmark. This is an architecture designed from first principles to produce verifiably correct reasoning — and it's doing so at levels beyond what the best human mathematicians can achieve.&lt;/p&gt;




&lt;h2&gt;
  
  
  LeCun's Bet — And Why It Matters
&lt;/h2&gt;

&lt;p&gt;LeCun's involvement isn't ceremonial. He has been the most prominent advocate for energy-based models in AI for over a decade. His position has been remarkably consistent across that entire period: autoregressive generation is a dead end for reasoning. The path to human-level AI requires architectures that can reason about constraints globally, not just generate sequences that are locally plausible.&lt;/p&gt;

&lt;p&gt;He laid out this argument in detail in his 2022 position paper "A Path Towards Autonomous Machine Intelligence," where he proposed a cognitive architecture built around a "world model" — essentially an energy-based model that learns to predict and evaluate states of the world — coordinated with other modules for perception, memory, and action. The Logical Intelligence architecture (EBRMs for reasoning, LLMs for language, Aleph for orchestration) is the closest practical implementation of that vision that has been publicly demonstrated.&lt;/p&gt;

&lt;p&gt;Most of the AI industry bet against this view. The reasoning was simple and, at the time, defensible: LLMs keep improving on benchmarks, so clearly the architecture is fine. Just scale it up. More parameters, more data, more GPUs. Hundreds of billions of dollars have been committed to this scaling hypothesis.&lt;/p&gt;

&lt;p&gt;The Sudoku demo — and PutnamBench, and the formal verification results — suggest a different answer. Scaling doesn't fix the architecture. It produces longer, more articulate chains of wrong reasoning. The failure mode is not insufficient capability; it's a fundamental mismatch between the architecture and the task.&lt;/p&gt;

&lt;p&gt;The vision isn't "EBMs replace LLMs." It's:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────┐
│        HUMAN INTERACTION LAYER       │
│    (LLMs — language understanding,   │
│     generation, coordination)        │
├─────────────────────────────────────┤
│       ORCHESTRATION LAYER            │
│    (Aleph — planning, tool use,      │
│     result verification)             │
├─────────────────────────────────────┤
│        REASONING CORE                │
│    (EBRMs — constraint satisfaction, │
│     formal verification, planning)   │
└─────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;LLMs generate candidates and handle human interaction — what they're architecturally suited for. EBRMs evaluate those candidates against formal constraints and guarantee correctness — what they're architecturally suited for. Orchestration coordinates between them, calling the right tool for the right sub-problem. Each component does what it was designed to do, rather than forcing one architecture to handle everything.&lt;/p&gt;

&lt;p&gt;This mirrors how human cognition works, at least at a high level. We have intuitive, language-based thinking (Kahneman's System 1) and deliberate, constraint-aware reasoning (System 2). We don't try to solve math problems by generating plausible-sounding words and hoping the constraints work out. We use different cognitive systems for different kinds of thinking. The compound AI architecture does the same.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Matters for Developers
&lt;/h2&gt;

&lt;p&gt;If you're building AI systems today, you've almost certainly encountered the "looks right but is wrong" failure mode. An LLM generates code that compiles and passes a quick review — but has a subtle logic error that only manifests under specific conditions. Or it produces a deployment plan that sounds perfectly reasonable but violates a security constraint you forgot to state explicitly.&lt;/p&gt;

&lt;p&gt;This is not a prompt engineering problem. The model has no mechanism for verifying global consistency because it wasn't designed to. Future iterations with more parameters will not fix this — they will produce the same class of error with higher confidence and more articulate justifications.&lt;/p&gt;

&lt;p&gt;What makes Logical Intelligence worth paying attention to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;It's not a research paper — it's a live product.&lt;/strong&gt; Load the demo at sudoku.logicalintelligence.com and test any frontier LLM against KONA yourself. The gap is not incremental. It is categorical.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The team is at the highest level.&lt;/strong&gt; Yann LeCun (Turing Award, Chief AI Scientist at Meta), Michael Freedman (Fields Medalist), Eve Bodnia (Founder and CEO). The combined mathematical and engineering depth is extraordinary for a company this early.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The cost differential exposes the architectural inefficiency.&lt;/strong&gt; The EBM solved the puzzle in 0.24 seconds running in-browser for free. The LLM that "succeeded" took 12x longer, cost real money, and produced the wrong answer. At scale — millions of reasoning queries per day — this difference compounds.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Constraint satisfaction is everywhere.&lt;/strong&gt; Code verification, medical diagnosis, legal reasoning, chip design, structural engineering, financial compliance, robotics, scheduling — these are all fundamentally constraint satisfaction problems where being wrong is expensive. Any domain where constraints are non-negotiable and errors cascade is a domain where autoregressive generation is structurally insufficient.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The architecture conversation in AI is about to become harder to ignore. LLMs are remarkable at what they do — language. But reasoning, in the formal sense — maintaining global consistency while navigating constraint spaces — may require a fundamentally different approach. Logical Intelligence is building one. And the fact that Yann LeCun is betting his institutional credibility on it should make everyone pay closer attention than the industry currently is.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;The demo is public and free:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sudoku demo:&lt;/strong&gt; &lt;a href="https://sudoku.logicalintelligence.com" rel="noopener noreferrer"&gt;sudoku.logicalintelligence.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Company site:&lt;/strong&gt; &lt;a href="https://logicalintelligence.com" rel="noopener noreferrer"&gt;logicalintelligence.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Technical blog posts:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://logicalintelligence.com/blog/energy-based-model-sudoku-demo" rel="noopener noreferrer"&gt;What Sudoku Reveals About AI Reasoning Architectures and the Future of Our Economy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://logicalintelligence.com/blog/energy-based-models-for-reasoning" rel="noopener noreferrer"&gt;Energy-Based Models for Reasoning, LLMs for the Interface: Scaling Reasoning with Agentic AI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://logicalintelligence.com/blog/aleph-solves-putnambench" rel="noopener noreferrer"&gt;Logical Intelligence's Aleph Solves PutnamBench&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Are energy-based models the next architecture shift, or a niche solution that LLMs will eventually absorb? I'm genuinely curious — especially from people building reasoning systems in production. Drop your take in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>programming</category>
      <category>architecture</category>
    </item>
    <item>
      <title>How I Self-Hosted Postiz with Tailscale (and the Env Vars the Docs Don't Tell You About)</title>
      <dc:creator>Aryan Iyappan</dc:creator>
      <pubDate>Wed, 03 Jun 2026 08:22:47 +0000</pubDate>
      <link>https://dev.to/aryaniyaps/how-i-self-hosted-postiz-with-tailscale-and-the-env-vars-the-docs-dont-tell-you-about-4i8g</link>
      <guid>https://dev.to/aryaniyaps/how-i-self-hosted-postiz-with-tailscale-and-the-env-vars-the-docs-dont-tell-you-about-4i8g</guid>
      <description>&lt;h1&gt;
  
  
  How I Self-Hosted Postiz with Tailscale (and the Env Vars the Docs Don't Tell You About)
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;A step-by-step guide to running your own social media scheduler behind Tailscale — with the gotchas that cost me an afternoon.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;I wanted a social media scheduler that I control. Not another SaaS subscription. Not another dashboard I log into that owns my data. Postiz is the best open-source option out there — 28+ platform integrations, a CLI, and a clean UI — but self-hosting it has a learning curve.&lt;/p&gt;

&lt;p&gt;The docs will get you 80% of the way there. The other 20% — the env vars nobody mentions, the Tailscale networking trick, the Temporal stack — is what this post covers.&lt;/p&gt;

&lt;p&gt;By the end, you'll have Postiz running on your server, accessible from anywhere via Tailscale's MagicDNS, with automatic HTTPS. No port forwarding. No Cloudflare Tunnels. No domain registrar.&lt;/p&gt;




&lt;h2&gt;
  
  
  What We're Building
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────────────────────────┐
│                Your Server                     │
│                                                │
│  ┌──────────┐    ┌─────────────────────────┐  │
│  │ Tailscale │◄───│ network_mode: service    │  │
│  │ Container │    │ (Postiz rides Tailscale's │  │
│  │           │    │  network stack)           │  │
│  │ :443→5000 │    └─────────────────────────┘  │
│  └──────────┘              │                   │
│       │                    ▼                   │
│       │           ┌─────────────────┐          │
│       │           │   Postiz App    │          │
│       │           │   (port 5000)   │          │
│       │           └────────┬────────┘          │
│       │                    │                   │
│       ▼                    ▼                   │
│  MagicDNS HTTPS    ┌──────────────┐            │
│  postiz.tailXXXX   │  PostgreSQL  │            │
│  .ts.net           │  Redis       │            │
│                    │  Temporal    │            │
│                    └──────────────┘            │
└──────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key insight: Postiz doesn't expose any ports to the host. Instead, it uses &lt;code&gt;network_mode: service:tailscale&lt;/code&gt; to piggyback on Tailscale's network stack. Tailscale handles DNS, HTTPS certificates, and the encrypted tunnel. Postiz just thinks it's serving HTTP on localhost.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A Linux server (Ubuntu 24.04 recommended) with at least &lt;strong&gt;2GB RAM&lt;/strong&gt; and &lt;strong&gt;2 vCPUs&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Docker and Docker Compose installed&lt;/li&gt;
&lt;li&gt;A [Tailscale](undefined account (free tier is fine)&lt;/li&gt;
&lt;li&gt;Basic comfort with editing YAML files&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 1: Get Your Tailscale Auth Key
&lt;/h2&gt;

&lt;p&gt;This is the key that lets your container join your Tailnet without manual authentication.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to [Tailscale Admin Console → Settings → Keys](undefined&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Generate auth key&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Set it to &lt;strong&gt;Reusable&lt;/strong&gt; and &lt;strong&gt;Ephemeral: Off&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Add a tag like &lt;code&gt;tag:containers&lt;/code&gt; (you'll need this in your ACL later)&lt;/li&gt;
&lt;li&gt;Copy the key — it starts with &lt;code&gt;tskey-auth-&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The free tier gives you 3 users and 100 devices. One container = one device, so you've got plenty of room.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Clone the Postiz Docker Compose Repo
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone undefined
&lt;span class="nb"&gt;cd &lt;/span&gt;postiz-docker-compose
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you the base &lt;code&gt;docker-compose.yaml&lt;/code&gt; plus the Temporal dynamic config files. Don't run it yet — we're going to modify it significantly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: The Tailscale Service (This Is the Secret Sauce)
&lt;/h2&gt;

&lt;p&gt;Add this service to your &lt;code&gt;docker-compose.yaml&lt;/code&gt;. It sits &lt;strong&gt;above&lt;/strong&gt; Postiz in the network stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;tailscale&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tailscale/tailscale:latest&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postiz-tailscale&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postiz&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;TS_AUTHKEY=tskey-auth-xxxxxxxxxxxxxxxxxxxx&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;TS_EXTRA_ARGS=--advertise-tags=tag:containers&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;TS_SERVE_CONFIG=/config/ts.json&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;TS_STATE_DIR=/var/lib/tailscale&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./tailscale-state:/var/lib/tailscale&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./tailscale-config:/config&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/dev/net/tun:/dev/net/tun&lt;/span&gt;
    &lt;span class="na"&gt;cap_add&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;NET_ADMIN&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;NET_RAW&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD-SHELL"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tailscale&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--peers=false&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--json&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;grep&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-q&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'Online.*true'"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;15s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
      &lt;span class="na"&gt;start_period&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postiz-network&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;temporal-network&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's happening here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;TS_AUTHKEY&lt;/code&gt;&lt;/strong&gt; authenticates the container to your Tailnet&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;TS_EXTRA_ARGS&lt;/code&gt;&lt;/strong&gt; advertises the &lt;code&gt;tag:containers&lt;/code&gt; ACL tag&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;TS_SERVE_CONFIG&lt;/code&gt;&lt;/strong&gt; points to a JSON file that tells Tailscale how to route traffic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/dev/net/tun&lt;/code&gt;&lt;/strong&gt; is the kernel tunnel device — required for Tailscale to create its virtual network interface&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;NET_ADMIN&lt;/code&gt;&lt;/strong&gt; and &lt;code&gt;NET_RAW&lt;/code&gt;** capabilities let Tailscale manipulate the network stack&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;healthcheck&lt;/strong&gt; waits for Tailscale to report &lt;code&gt;Online: true&lt;/code&gt; before Postiz starts&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Tailscale Serve Config
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;tailscale-config/ts.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"TCP"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"443"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"HTTPS"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Web"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"postiz.taila7d0df.ts.net:443"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Handlers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"Proxy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"undefined"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells Tailscale:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Listen on port 443 (HTTPS)&lt;/li&gt;
&lt;li&gt;When a request comes in for &lt;code&gt;postiz.tailXXXX.ts.net&lt;/code&gt;, proxy it to &lt;code&gt;undefined&lt;/code&gt; (where Postiz listens)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Tailscale automatically provisions a Let's Encrypt certificate&lt;/strong&gt; for &lt;code&gt;*.ts.net&lt;/code&gt; domains. You get HTTPS with zero configuration.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ Replace &lt;code&gt;postiz.taila7d0df.ts.net&lt;/code&gt; with your actual Tailscale MagicDNS name. You can find it in the Tailscale admin console under the machine's details, or run &lt;code&gt;tailscale status&lt;/code&gt; once the container is up.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 4: The Postiz Service — Env Vars That Actually Matter
&lt;/h2&gt;

&lt;p&gt;Here's the Postiz service, modified to work with Tailscale. This is where the docs fall short.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;postiz&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/gitroomhq/postiz-app:v2.21.8&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postiz&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;network_mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service:tailscale&lt;/span&gt;   &lt;span class="c1"&gt;# ← THIS IS THE KEY LINE&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# === URLs — MUST match your Tailscale MagicDNS name&lt;/span&gt;
      &lt;span class="na"&gt;MAIN_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;undefined'&lt;/span&gt;
      &lt;span class="na"&gt;FRONTEND_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;undefined'&lt;/span&gt;
      &lt;span class="na"&gt;NEXT_PUBLIC_BACKEND_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;undefined'&lt;/span&gt;

      &lt;span class="c1"&gt;# === Database &amp;amp; Redis&lt;/span&gt;
      &lt;span class="na"&gt;JWT_SECRET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;your-random-secret-string-here'&lt;/span&gt;
      &lt;span class="na"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;postgresql://postiz-user:postiz-password@postiz-postgres:5432/postiz-db-local'&lt;/span&gt;
      &lt;span class="na"&gt;REDIS_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;redis://postiz-redis:6379'&lt;/span&gt;

      &lt;span class="c1"&gt;# === Internal Communication (CRITICAL!)&lt;/span&gt;
      &lt;span class="na"&gt;BACKEND_INTERNAL_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;http://localhost:3000'&lt;/span&gt;
      &lt;span class="na"&gt;TEMPORAL_ADDRESS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;temporal:7233'&lt;/span&gt;

      &lt;span class="c1"&gt;# === Feature Flags&lt;/span&gt;
      &lt;span class="na"&gt;IS_GENERAL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;true'&lt;/span&gt;
      &lt;span class="na"&gt;DISABLE_REGISTRATION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;false'&lt;/span&gt;
      &lt;span class="na"&gt;RUN_CRON&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;true'&lt;/span&gt;
      &lt;span class="na"&gt;NOT_SECURED&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;false'&lt;/span&gt;

      &lt;span class="c1"&gt;# === Storage (Cloudflare R2)&lt;/span&gt;
      &lt;span class="na"&gt;STORAGE_PROVIDER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cloudflare'&lt;/span&gt;
      &lt;span class="na"&gt;CLOUDFLARE_ACCOUNT_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;your-account-id'&lt;/span&gt;
      &lt;span class="na"&gt;CLOUDFLARE_ACCESS_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;your-access-key'&lt;/span&gt;
      &lt;span class="na"&gt;CLOUDFLARE_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;your-secret-key'&lt;/span&gt;
      &lt;span class="na"&gt;CLOUDFLARE_BUCKETNAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;your-bucket-name'&lt;/span&gt;
      &lt;span class="na"&gt;CLOUDFLARE_BUCKET_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;undefined'&lt;/span&gt;
      &lt;span class="na"&gt;CLOUDFLARE_REGION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;auto'&lt;/span&gt;

      &lt;span class="c1"&gt;# === Platform API Keys (fill in the platforms you use)&lt;/span&gt;
      &lt;span class="na"&gt;X_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;
      &lt;span class="na"&gt;X_API_SECRET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;
      &lt;span class="na"&gt;LINKEDIN_CLIENT_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;
      &lt;span class="na"&gt;LINKEDIN_CLIENT_SECRET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;
      &lt;span class="c1"&gt;# ... (add keys for platforms you want to use)&lt;/span&gt;

      &lt;span class="c1"&gt;# === Misc&lt;/span&gt;
      &lt;span class="na"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;
      &lt;span class="na"&gt;API_LIMIT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;

    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postiz-config:/config/&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postiz-uploads:/uploads/&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;tailscale&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;
      &lt;span class="na"&gt;postiz-postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;
      &lt;span class="na"&gt;postiz-redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Env Vars That Cost Me an Afternoon
&lt;/h3&gt;

&lt;p&gt;Here's what the official docs don't emphasize enough:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. &lt;code&gt;BACKEND_INTERNAL_URL: 'http://localhost:3000'&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;This is the most important env var you've never heard of.&lt;/strong&gt; Postiz's server-side code makes API calls to itself. If this is wrong, OAuth callbacks fail silently, image uploads break, and you get mysterious 500 errors with no useful logs.&lt;/p&gt;

&lt;p&gt;Always set this to &lt;code&gt;http://localhost:3000&lt;/code&gt; — &lt;strong&gt;not&lt;/strong&gt; your public URL. The app listens on port 3000 internally. The &lt;code&gt;network_mode: service:tailscale&lt;/code&gt; trick means Tailscale's port 5000 (or 443) proxies to this internal port. But the &lt;em&gt;app itself&lt;/em&gt; needs to reach itself at &lt;code&gt;localhost:3000&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. &lt;code&gt;NEXT_PUBLIC_BACKEND_URL&lt;/code&gt; &lt;strong&gt;MUST&lt;/strong&gt; end with &lt;code&gt;/api&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;This is the URL your browser uses to talk to the backend. If you forget the &lt;code&gt;/api&lt;/code&gt; suffix, the frontend loads but every API call returns a 404. You'll stare at a blank dashboard wondering why nothing works.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✅ NEXT_PUBLIC_BACKEND_URL: 'undefined'
❌ NEXT_PUBLIC_BACKEND_URL: 'undefined'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;/api&lt;/code&gt; suffix is how the Next.js frontend routes API requests. Without it, the frontend sends requests to &lt;code&gt;/graphql&lt;/code&gt; instead of &lt;code&gt;/api/graphql&lt;/code&gt;, and nothing resolves.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. &lt;code&gt;MAIN_URL&lt;/code&gt; and &lt;code&gt;FRONTEND_URL&lt;/code&gt; must be identical
&lt;/h4&gt;

&lt;p&gt;In most web apps, these can be different. In Postiz, they interact with OAuth redirects, cookie domains, and CORS in ways that are not fully documented. Set them both to your full HTTPS URL and save yourself the debugging.&lt;/p&gt;

&lt;h4&gt;
  
  
  4. &lt;code&gt;RUN_CRON: 'true'&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Without this, your scheduled posts will never publish. Postiz uses an internal cron system (backed by Temporal) to fire posts at their scheduled time. If this is &lt;code&gt;false&lt;/code&gt; or missing, posts sit in the queue forever.&lt;/p&gt;

&lt;h4&gt;
  
  
  5. &lt;code&gt;NOT_SECURED: 'false'&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;When running behind a reverse proxy (Tailscale, in our case), this tells Postiz "trust the upstream HTTPS, don't enforce your own." If you set it to &lt;code&gt;true&lt;/code&gt;, you'll get redirect loops and mixed content warnings.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: The Supporting Cast (PostgreSQL, Redis, Temporal)
&lt;/h2&gt;

&lt;p&gt;Postiz needs Temporal for scheduling. The minimum stack looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;postiz-postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:17-alpine&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postiz-postgres&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postiz-password&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postiz-user&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postiz-db-local&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres-volume:/var/lib/postgresql/data&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postiz-network&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pg_isready -U postiz-user -d postiz-db-local&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;

  &lt;span class="na"&gt;postiz-redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:7.2&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postiz-redis&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis-cli ping&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postiz-redis-data:/data&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postiz-network&lt;/span&gt;

  &lt;span class="c1"&gt;# Temporal stack (required for scheduled publishing)&lt;/span&gt;
  &lt;span class="na"&gt;temporal-postgresql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;temporal-postgresql&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:16&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;temporal&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;temporal&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;temporal-network&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/var/lib/postgresql/data&lt;/span&gt;

  &lt;span class="na"&gt;temporal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;temporal&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;7233:7233'&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;temporalio/auto-setup:1.28.1&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;temporal-postgresql&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DB=postgres12&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DB_PORT=5432&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_USER=temporal&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PWD=temporal&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_SEEDS=temporal-postgresql&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DYNAMIC_CONFIG_FILE_PATH=config/dynamicconfig/development-sql.yaml&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;temporal-network&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./dynamicconfig:/etc/temporal/config/dynamicconfig&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why Temporal?&lt;/strong&gt; Postiz uses Temporal for its background job engine. When you schedule a post for "tomorrow at 2 PM," a Temporal workflow sleeps until the right time, then fires the publishing pipeline. Without Temporal running, cron jobs work but scheduled publishing silently fails.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Resource tip:&lt;/strong&gt; Temporal + its PostgreSQL can consume 500MB-1GB RAM. If you're on a tight VPS, consider running Temporal on a separate machine or scaling down to 1GB RAM (set &lt;code&gt;ES_JAVA_OPTS=-Xms100m -Xmx100m&lt;/code&gt; if using Elasticsearch).&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 6: Networks and Volumes
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;postgres-volume&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;postiz-redis-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;postiz-config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;postiz-uploads&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;postiz-network&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;temporal-network&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;temporal-network&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two separate networks keep Postiz app data and Temporal data isolated. The Tailscale container bridges both networks so Postiz (which rides on Tailscale) can reach Temporal at &lt;code&gt;temporal:7233&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 7: Bring It Up
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Make the directories Tailscale needs&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; tailscale-state tailscale-config

&lt;span class="c"&gt;# Create the Tailscale serve config (edit the hostname!)&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; tailscale-config/ts.json &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
{
  "TCP": {
    "443": {
      "HTTPS": true
    }
  },
  "Web": {
    "postiz.YOUR-TAILNET.ts.net:443": {
      "Handlers": {
        "/": {
          "Proxy": "undefined"
        }
      }
    }
  }
}
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="c"&gt;# Start everything&lt;/span&gt;
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait about 60-90 seconds for everything to initialize. Tailscale needs to authenticate, provision certificates, and report healthy. Postgres and Redis need to accept connections. Temporal needs to set up its schemas.&lt;/p&gt;

&lt;p&gt;Watch the logs to see what's happening:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Watch all services&lt;/span&gt;
docker compose logs &lt;span class="nt"&gt;-f&lt;/span&gt;

&lt;span class="c"&gt;# Just Postiz&lt;/span&gt;
docker compose logs &lt;span class="nt"&gt;-f&lt;/span&gt; postiz

&lt;span class="c"&gt;# Just Tailscale (see if it authenticated)&lt;/span&gt;
docker compose logs &lt;span class="nt"&gt;-f&lt;/span&gt; tailscale
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Verifying Everything Works
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Check Tailscale connectivity
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;exec &lt;/span&gt;postiz-tailscale tailscale status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the container listed as &lt;code&gt;online&lt;/code&gt; with your MagicDNS name.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Check Postiz health
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-I&lt;/span&gt; undefined
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Should return &lt;code&gt;HTTP/2 200&lt;/code&gt;. If it hangs, Tailscale isn't online yet — check the logs.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Open it in your browser
&lt;/h3&gt;

&lt;p&gt;Navigate to &lt;code&gt;undefined&lt;/code&gt;. You should see the Postiz login page. Register your admin account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; After registering, immediately add your social platform integrations from the dashboard. The platform API keys you put in &lt;code&gt;docker-compose.yaml&lt;/code&gt; enable backend communication, but you still need to connect each platform through the UI.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Complete Gotcha List
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Gotcha&lt;/th&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Missing &lt;code&gt;/api&lt;/code&gt; in &lt;code&gt;NEXT_PUBLIC_BACKEND_URL&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Dashboard loads but no data, 404s in console&lt;/td&gt;
&lt;td&gt;Add &lt;code&gt;/api&lt;/code&gt; suffix&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wrong &lt;code&gt;BACKEND_INTERNAL_URL&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;OAuth callbacks fail, uploads silently break&lt;/td&gt;
&lt;td&gt;Set to &lt;code&gt;http://localhost:3000&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;RUN_CRON&lt;/code&gt; not set&lt;/td&gt;
&lt;td&gt;Scheduled posts never publish&lt;/td&gt;
&lt;td&gt;Add &lt;code&gt;RUN_CRON: 'true'&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;MAIN_URL&lt;/code&gt; ≠ &lt;code&gt;FRONTEND_URL&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;CORS errors, cookie issues&lt;/td&gt;
&lt;td&gt;Make them identical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tailscale healthcheck too aggressive&lt;/td&gt;
&lt;td&gt;Postiz starts before Tailscale is ready&lt;/td&gt;
&lt;td&gt;Increase &lt;code&gt;start_period&lt;/code&gt; to 60s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Port 5000 exposed directly&lt;/td&gt;
&lt;td&gt;Bypasses Tailscale entirely&lt;/td&gt;
&lt;td&gt;Comment out/remove &lt;code&gt;ports&lt;/code&gt; from Postiz&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Temporal not running&lt;/td&gt;
&lt;td&gt;Scheduling silently fails&lt;/td&gt;
&lt;td&gt;Ensure &lt;code&gt;TEMPORAL_ADDRESS&lt;/code&gt; points to the right container&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Why Go Through All This?
&lt;/h2&gt;

&lt;p&gt;You could use Postiz Cloud. It's great. But here's why I self-host:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Data ownership.&lt;/strong&gt; Every API token, every scheduled post, every analytics data point lives on &lt;em&gt;my&lt;/em&gt; server. Not someone else's.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No monthly SaaS fee.&lt;/strong&gt; The only costs are my VPS and API usage. Postiz itself is free.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tailscale means zero firewall configuration.&lt;/strong&gt; I don't expose port 443 to the internet. I don't configure nginx. Tailscale's WireGuard tunnel handles all of it. My server could sit behind a NAT with no public IP and this would still work.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Platform API key security.&lt;/strong&gt; All API keys live in environment variables on my server. They never touch Postiz's cloud. Given that these keys have write access to my social accounts, this matters.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Learning.&lt;/strong&gt; Setting this up taught me more about Docker networking, Tailscale Serve, and Next.js deployment patterns than any tutorial could.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;Now that Postiz is running, here's what I'm building on top of it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hermes Agent integration:&lt;/strong&gt; An AI agent that drafts posts using my voice, uploads them via the Postiz CLI, and schedules them. Zero manual posting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analytics pipeline:&lt;/strong&gt; Postiz's built-in analytics fed into a dashboard that tracks what's working across platforms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-user setup:&lt;/strong&gt; Adding collaborators through Postiz's team features, all behind the same Tailscale barrier.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;What about you?&lt;/strong&gt; Are you self-hosting your social media tooling, or do you trust the cloud providers? I'm genuinely curious — every self-hoster I've met has a story about the one time their config broke at 2 AM. Drop yours in the comments.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>opensource</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
