<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Postgres – Serverküche</title><link>https://serverkueche.de/tags/postgres/</link><description>Postgres – Neueste Beiträge von Serverküche</description><generator>Hugo</generator><language>de-DE</language><managingEditor>feedback@serverkueche.de (Serverküche)</managingEditor><webMaster>feedback@serverkueche.de (Serverküche)</webMaster><copyright>2026 Serverküche</copyright><lastBuildDate>Tue, 03 Nov 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://serverkueche.de/tags/postgres/index.xml" rel="self" type="application/rss+xml"/><item><title>HedgeDoc: gemeinsame Markdown-Notizen in Echtzeit</title><link>https://serverkueche.de/tutorials/hedgedoc-markdown-notizen/</link><pubDate>Tue, 03 Nov 2026 00:00:00 +0000</pubDate><author>feedback@serverkueche.de (Serverküche)</author><guid>https://serverkueche.de/tutorials/hedgedoc-markdown-notizen/</guid><description>HedgeDoc 1.12 mit Postgres 18 hinter Traefik: Notizen zu zweit gleichzeitig tippen, Links teilen – und die Freigaben so einstellen, dass sie dicht sind.</description><content:encoded><![CDATA[<p>Ein Etherpad, das Markdown kann: Zwei Leute tippen gleichzeitig in dieselbe Notiz, rechts entsteht
live die formatierte Fassung, und am Ende verschickst du einen Link. HedgeDoc macht genau das – und
liefert dabei ein paar Voreinstellungen mit, die du kennen solltest, bevor die Notiz im Netz steht.</p>
<h2 id="was-bauen-wir">Was bauen wir?</h2>
<p>Wir betreiben <strong>HedgeDoc 1.12.0</strong> mit <strong>PostgreSQL 18.6</strong> hinter Traefik. Am Ende hast du einen
Editor, in dem mehrere Leute gleichzeitig an derselben Markdown-Notiz arbeiten, mit Tabellen,
Aufgabenlisten und einer Live-Vorschau. Notizen lassen sich als Link weitergeben – lesend oder
schreibend, ganz wie du es einstellst.</p>
<p>Der zweite Teil ist wichtiger als der erste: HedgeDoc kommt <strong>offen</strong> aus der Kiste. Jeder darf sich
registrieren, und jeder Besucher darf Notizen anlegen – auch ohne Konto. Wir drehen das um –
geschlossen als Grundzustand, geöffnet pro Notiz – und schauen dabei genau hin, weil zwei der
Einstellungen nicht das tun, was ihr Name verspricht.</p>
<h2 id="voraussetzungen">Voraussetzungen</h2>
<ul>
<li><a href="/tutorials/reverse-proxy-traefik/">Traefik als Reverse Proxy</a> mit funktionierendem
Let&rsquo;s-Encrypt-Resolver und einer Domain, die auf den Server zeigt</li>
<li>Die Domain musst du <strong>vorher</strong> kennen: HedgeDoc baut alle Links aus einer fest konfigurierten
Domain, nachträgliches Umziehen ist unangenehm</li>
<li>Etwa 1 GB freier Arbeitsspeicher für Anwendung und Datenbank zusammen</li>
</ul>
<div class="not-prose my-6 overflow-hidden rounded-xl border border-paprika-200 bg-paprika-50 dark:border-paprika-800 dark:bg-paprika-900/20"
     data-track-content data-content-name="Affiliate-Box · /tutorials/hedgedoc-markdown-notizen/" data-content-piece="VPS 1000 G12.5">
  <div class="flex items-center justify-between border-b border-paprika-200 bg-paprika-100 px-4 py-1.5 text-xs font-semibold uppercase tracking-wide text-paprika-700 dark:border-paprika-800 dark:bg-paprika-900/40 dark:text-paprika-300">
    <span>🍳 Empfehlung</span>
    <span title="Mit * markierte Links sind Affiliate-Links.">Anzeige</span>
  </div>
  <div class="flex flex-col gap-4 p-4 sm:flex-row sm:items-center sm:justify-between">
    <div>
      <p class="text-lg font-bold text-slate-900 dark:text-white">VPS 1000 G12.5</p>
      <p class="mt-1 text-sm text-slate-600 dark:text-slate-300">4 vCore · 8 GB RAM · 128 GB SSD</p>
      <p class="mt-1 text-sm font-semibold text-paprika-700 dark:text-paprika-400">ab 14,50 €/Monat</p>
      <p class="mt-2 text-sm text-slate-600 dark:text-slate-400">HedgeDoc und Postgres laufen zusammen bequem auf dem kleinen VPS.</p>
    </div>
    <a href="https://www.netcup.com/de/server/vps/vps-1000-g12.5-24m-eu?ref=44083#vps-1000-g12.5-12m-eu" rel="sponsored noopener" target="_blank"
   data-track-event="Affiliate|netcup: Affiliate-Box|VPS 1000 G12.5 · {page}"
   class="inline-flex shrink-0 items-center justify-center rounded-lg bg-paprika-600 px-5 py-2.5 font-semibold text-white transition-colors hover:bg-paprika-700">
  Zu netcup →
</a>

  </div><div class="px-4 pb-4"><div class="not-prose my-3 rounded-lg border border-herb-500/40 bg-herb-50 px-3 py-2 text-sm text-slate-700 dark:bg-herb-900/20 dark:text-slate-200"
     data-voucher-api="vps-1000" data-voucher-msg-empty="Gerade ist für diesen Tarif kein Code frei – nimm den 5,95-€-Code." data-voucher-msg-limit="Für heute sind keine weiteren Codes drin – nimm den 5,95-€-Code oder versuch es morgen." data-voucher-msg-error="Der Code ließ sich gerade nicht abrufen – nimm den 5,95-€-Code."
     data-voucher-hint="Warenkorb vorher leeren – der Code legt den Server selbst hinein.">
  <p class="flex flex-wrap items-center gap-x-2 gap-y-1"><span>💶 <span class="font-semibold">1 Monat gratis</span>:</span>
    <button type="button" data-voucher-reveal data-copy-title="Zum Kopieren klicken"
            class="cursor-pointer rounded bg-white px-2 py-0.5 font-mono text-sm font-semibold text-herb-800 hover:ring-2 hover:ring-herb-500/40 dark:bg-slate-800 dark:text-herb-400">Code anzeigen</button>
    <button type="button" data-voucher-next hidden
            class="rounded border border-herb-500/40 px-2 py-0.5 text-xs text-herb-800 hover:bg-white dark:text-herb-400 dark:hover:bg-slate-800">anderer Code</button>
  </p><p class="mt-1 text-xs text-slate-500 dark:text-slate-400" data-voucher-hint-target>
    Einmal einlösbar, gilt für VPS 1000 G12.5.
    <a href="https://www.netcup.com/de/checkout/warenkorb?ref=44083" rel="sponsored noopener" target="_blank"
       data-track-event="Affiliate|netcup: Gutschein einlösen|VPS 1000 G12.5 · {page}"
       class="font-medium text-herb-800 underline underline-offset-2 hover:text-herb-900 dark:text-herb-400">Im Warenkorb einlösen →</a>
  </p><p class="mt-2 flex flex-wrap items-center gap-x-2 gap-y-1 border-t border-herb-500/30 pt-2">
    <span>💶 <span class="font-semibold">5,95 € Gutschein</span> für netcup-Neukunden:</span>
    <button type="button" data-voucher-code data-track-voucher="36nc17844976032"
            title="Zum Kopieren klicken" class="cursor-pointer rounded bg-white px-2 py-0.5 font-mono text-sm font-semibold text-herb-800 hover:ring-2 hover:ring-herb-500/40 dark:bg-slate-800 dark:text-herb-400">36nc17844976032</button>
    <span class="text-xs text-slate-500 dark:text-slate-400">immer gültig · nicht für Domains und VPS Lite</span>
  </p><p class="mt-1 text-xs"><a href="/netcup-gutschein/" class="font-medium text-herb-800 underline underline-offset-2 hover:text-herb-900 dark:text-herb-400">Alle netcup-Gutscheine →</a></p>
</div></div>
</div>

<h2 id="schritt-für-schritt">Schritt für Schritt</h2>
<h3 id="schritt-1-der-stack--mit-einer-aktuellen-postgres-falle">Schritt 1: Der Stack – mit einer aktuellen Postgres-Falle</h3>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir -p /opt/hedgedoc <span class="o">&amp;&amp;</span> <span class="nb">cd</span> /opt/hedgedoc</span></span></code></pre></div>
</div>
<p>Zuerst die Geheimnisse. HedgeDoc signiert damit die Sitzungs-Cookies; ohne eigenen Wert nimmt es
einen zufälligen, und alle Anmeldungen fliegen bei jedem Neustart raus:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">umask</span> <span class="m">077</span>
</span></span><span class="line"><span class="cl"><span class="o">{</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;POSTGRES_PASSWORD=</span><span class="k">$(</span>openssl rand -base64 <span class="m">24</span> <span class="p">|</span> tr -d <span class="s1">&#39;=+/&#39;</span> <span class="p">|</span> cut -c1-24<span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;CMD_SESSION_SECRET=</span><span class="k">$(</span>openssl rand -hex 32<span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span> &gt; .env</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">YAML</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># /opt/hedgedoc/compose.yaml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">database</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">postgres:18.6-alpine</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">hedgedoc-db</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">POSTGRES_USER</span><span class="p">:</span><span class="w"> </span><span class="l">hedgedoc</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">POSTGRES_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">${POSTGRES_PASSWORD}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">POSTGRES_DB</span><span class="p">:</span><span class="w"> </span><span class="l">hedgedoc</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">pgdata:/var/lib/postgresql</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">hedgedoc-intern]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">healthcheck</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">test</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;CMD-SHELL&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;pg_isready -U hedgedoc&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">interval</span><span class="p">:</span><span class="w"> </span><span class="l">10s</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">timeout</span><span class="p">:</span><span class="w"> </span><span class="l">5s</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">retries</span><span class="p">:</span><span class="w"> </span><span class="m">5</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">app</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">quay.io/hedgedoc/hedgedoc:1.12.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">hedgedoc</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">database</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">service_healthy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">CMD_DB_URL</span><span class="p">:</span><span class="w"> </span><span class="l">postgres://hedgedoc:${POSTGRES_PASSWORD}@database:5432/hedgedoc</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">CMD_SESSION_SECRET</span><span class="p">:</span><span class="w"> </span><span class="l">${CMD_SESSION_SECRET}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">CMD_DOMAIN</span><span class="p">:</span><span class="w"> </span><span class="l">DEINE_DOMAIN</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">CMD_PROTOCOL_USESSL</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;true&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">CMD_URL_ADDPORT</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;false&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">CMD_ALLOW_EMAIL_REGISTER</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;false&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">CMD_ALLOW_ANONYMOUS</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;false&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">CMD_DEFAULT_PERMISSION</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;limited&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">CMD_ALLOW_FREEURL</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;true&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./uploads:/hedgedoc/public/uploads</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">hedgedoc-intern, proxy]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">traefik.enable=true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">traefik.http.routers.hedgedoc.rule=Host(`DEINE_DOMAIN`)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">traefik.http.routers.hedgedoc.entrypoints=websecure</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">traefik.http.routers.hedgedoc.tls.certresolver=le</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">traefik.http.services.hedgedoc.loadbalancer.server.port=3000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pgdata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">networks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">hedgedoc-intern</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">internal</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">proxy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">external</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span></span></span></code></pre></div>
</div>
<p>Zwei Dinge an diesem Stack sind kein Zufall. Die Datenbank hängt in einem <strong>eigenen, internen Netz</strong>
(<code>internal: true</code>) und ist damit weder von außen noch von anderen Containern im <code>proxy</code>-Netz
erreichbar – nur HedgeDoc, das in beiden Netzen steckt, kommt heran. Und der <code>healthcheck</code> samt
<code>depends_on: condition: service_healthy</code> verhindert, dass HedgeDoc startet, bevor Postgres bereit
ist; sonst bricht es beim ersten Verbindungsversuch ab.</p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-amber-400 bg-amber-50 dark:border-amber-700 dark:bg-amber-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">⚠️</span>PostgreSQL 18 hat den Datenpfad geändert
  </p>
  <div class="prose-kitchen text-sm"><p>Ältere Anleitungen – auch die offizielle Compose-Datei des HedgeDoc-Projekts – hängen die
Datenbank unter <code>/var/lib/postgresql/**data**</code> ein. Mit PostgreSQL 18 startet der Container so
<strong>gar nicht mehr</strong>, selbst mit leerem Volume:</p>
<p><code>Error: in 18+, these Docker images are configured to store database data in a format which is compatible with &quot;pg_ctlcluster&quot; … The suggested container configuration for 18+ is to place a single mount at /var/lib/postgresql</code></p>
<p>Der Mount muss also eine Ebene höher, auf <code>/var/lib/postgresql</code>. Das gilt für benannte Volumes
genauso wie für Bind-Mounts.</p>
</div>
</div>
<p>Warum ein <strong>benanntes Volume</strong> und kein <code>./pgdata</code>-Ordner? Weil ein Bind-Mount am neuen Pfad dem
Container gehören muss und sonst mit <code>mkdir: can't create directory '/var/lib/postgresql/18/': Permission denied</code> scheitert – beim alten Pfad hat das Image das noch selbst geradegezogen. Für
Backups brauchst du den Ordner ohnehin nicht: Ein Datenbank-Backup macht man mit <code>pg_dump</code>, nicht
mit einer Dateikopie.</p>
<h3 id="schritt-2-die-variablen-die-über-links-und-sicherheit-entscheiden">Schritt 2: Die Variablen, die über Links und Sicherheit entscheiden</h3>
<p>Vier davon betreffen die <strong>Adressen</strong>, die HedgeDoc erzeugt:</p>
<ul>
<li><strong><code>CMD_DOMAIN</code></strong> – deine Domain, ohne Protokoll und ohne Pfad. Fehlt sie, stehen in verschickten
Links <code>localhost</code>.</li>
<li><strong><code>CMD_PROTOCOL_USESSL: &quot;true&quot;</code></strong> – erzeugt <code>https://</code>-Links. Der Container selbst spricht weiter
HTTP, das TLS macht Traefik.</li>
<li><strong><code>CMD_URL_ADDPORT: &quot;false&quot;</code></strong> – ohne das hängt HedgeDoc einen Port an jede URL.</li>
<li><strong><code>CMD_ALLOW_FREEURL: &quot;true&quot;</code></strong> – erlaubt sprechende Adressen wie <code>/kochabend</code> statt einer
Zufalls-ID: Wer eine noch freie Adresse aufruft, legt dort eine neue Notiz an. Mit der nächsten
Einstellung gilt das nur für angemeldete Nutzer.</li>
</ul>
<p>Die anderen drei entscheiden, wer überhaupt hereinkommt:</p>
<ul>
<li><strong><code>CMD_ALLOW_EMAIL_REGISTER: &quot;false&quot;</code></strong> – niemand kann sich selbst registrieren. Konten legst du
auf der Kommandozeile an (Schritt 4).</li>
<li><strong><code>CMD_ALLOW_ANONYMOUS: &quot;false&quot;</code></strong> – Gäste dürfen <strong>keine Notizen anlegen</strong>. Der Name führt in
die Irre: Lesen dürfen Gäste trotzdem, sofern die jeweilige Notiz das erlaubt (Schritt 6). Steht
der Wert auf <code>true</code> – so ist es ab Werk –, legt jeder Besucher über <strong>New</strong> oder eine freie
Adresse Notizen an, die <strong>niemandem gehören und für jeden bearbeitbar</strong> sind. Bots, die das Netz
nach WordPress absuchen, erzeugen so schon in den ersten Minuten Notizen wie <code>/wp-json</code> und
<code>/xmlrpc.php</code>. Nebenbei dürfen mit <code>false</code> nur noch angemeldete Nutzer Bilder hochladen.</li>
<li><strong><code>CMD_DEFAULT_PERMISSION: &quot;limited&quot;</code></strong> – neue Notizen sind für Gäste zu. Öffnen tust du sie
einzeln. Diese Vorgabe gilt nur für Notizen mit Besitzer; eine Gast-Notiz steht immer auf
„Freely“ – ein Grund mehr für <code>CMD_ALLOW_ANONYMOUS: &quot;false&quot;</code>.</li>
</ul>
<h3 id="schritt-3-starten-und-nachsehen-ob-es-dicht-ist">Schritt 3: Starten und nachsehen, ob es dicht ist</h3>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose up -d
</span></span><span class="line"><span class="cl">docker compose ps</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">NAME          STATUS
</span></span><span class="line"><span class="cl">hedgedoc      Up 25 seconds (healthy)
</span></span><span class="line"><span class="cl">hedgedoc-db   Up 50 seconds (healthy)</span></span></code></pre></div>
</div>
<p>Beide Container melden <code>healthy</code> – die Datenbank antwortet auf <code>pg_isready</code>, HedgeDoc auf seinen
eigenen Healthcheck. Jetzt die Gegenprobe von außen. HedgeDoc hat einen offenen Status-Endpunkt,
der sich gut zum Prüfen eignet:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -s https://DEINE_DOMAIN/status</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">{&#34;onlineNotes&#34;:0,&#34;onlineUsers&#34;:0,&#34;distinctOnlineUsers&#34;:0,&#34;notesCount&#34;:0,&#34;registeredUsers&#34;:0, …}</span></span></code></pre></div>
</div>
<p>Und die drei Türen, die zu sein sollen:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -s -o /dev/null -w <span class="s2">&#34;register: %{http_code}\n&#34;</span> -X POST https://DEINE_DOMAIN/register
</span></span><span class="line"><span class="cl">curl -s -o /dev/null -w <span class="s2">&#34;neue Notiz: %{http_code} %{redirect_url}\n&#34;</span> https://DEINE_DOMAIN/new
</span></span><span class="line"><span class="cl">curl -s -o /dev/null -w <span class="s2">&#34;freie URL: %{http_code} %{redirect_url}\n&#34;</span> https://DEINE_DOMAIN/kochabend</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">register: 404
</span></span><span class="line"><span class="cl">neue Notiz: 302 https://DEINE_DOMAIN/
</span></span><span class="line"><span class="cl">freie URL: 302 https://DEINE_DOMAIN/</span></span></code></pre></div>
</div>
<p><code>404</code> heißt: Die Registrierungsroute existiert gar nicht mehr. Bei <code>/new</code> und der freien Adresse
kommt es auf das <strong>Ziel</strong> der Weiterleitung an: Die Startseite heißt, der Gast wurde weggeschickt.
Zeigt die Weiterleitung dagegen auf eine neue Adresse wie <code>https://DEINE_DOMAIN/JBdRlegoRAqN…</code> oder
<code>https://DEINE_DOMAIN/kochabend</code>, hat HedgeDoc gerade eine Notiz angelegt – dann steht
<code>CMD_ALLOW_ANONYMOUS</code> noch auf <code>true</code>. Die Gegenprobe ist <code>notesCount</code> im Status-Endpunkt: Der muss
bei <code>0</code> bleiben, solange du selbst nichts angelegt hast.</p>
<h3 id="schritt-4-ein-konto-anlegen--auf-der-kommandozeile">Schritt 4: Ein Konto anlegen – auf der Kommandozeile</h3>
<p>Weil die Selbstregistrierung zu ist, legst du Konten mit dem mitgelieferten Werkzeug an. <strong>Achte auf
die Reihenfolge der Argumente</strong> – <code>--pass</code> steht vorn, sonst zeigt das Skript nur seine Hilfe:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose <span class="nb">exec</span> app bin/manage_users --pass DEIN_PASSWORT --add DEINE@MAILADRESSE</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Using password from commandline...
</span></span><span class="line"><span class="cl">Created user with email DEINE@MAILADRESSE</span></span></code></pre></div>
</div>
<p>Dass es geklappt hat, bestätigt der Status-Endpunkt:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -s https://DEINE_DOMAIN/status <span class="p">|</span> grep -o <span class="s1">&#39;&#34;registeredUsers&#34;:[0-9]*&#39;</span></span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">&#34;registeredUsers&#34;:1</span></span></code></pre></div>
</div>
<p>Auf der Startseite gibt es jetzt nur noch <strong>Sign In</strong> – keinen Registrieren-Knopf:</p>
<p><figure class="my-6"><img src="/tutorials/hedgedoc-markdown-notizen/hd-signin_hu_37a9cb1c5e60b279.webp" srcset="/tutorials/hedgedoc-markdown-notizen/hd-signin_hu_887d8bdbb892268f.webp 480w, /tutorials/hedgedoc-markdown-notizen/hd-signin_hu_37a9cb1c5e60b279.webp 768w, /tutorials/hedgedoc-markdown-notizen/hd-signin_hu_4455750d3345cccc.webp 1200w, /tutorials/hedgedoc-markdown-notizen/hd-signin_hu_3457b239af456975.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/tutorials/hedgedoc-markdown-notizen/hd-signin_hu_cad4d6633a46392c.webp"
    alt="Der Anmeldedialog von HedgeDoc mit den Feldern E-Mail und Passwort, ohne Möglichkeit zur Registrierung." title="Anmelden ja, registrieren nein – Konten legt der Administrator an"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">Anmelden ja, registrieren nein – Konten legt der Administrator an</figcaption></figure></p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-amber-400 bg-amber-50 dark:border-amber-700 dark:bg-amber-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">⚠️</span>Achtung
  </p>
  <div class="prose-kitchen text-sm">Weitere Konten kosten dich einen Befehl, aber sie sind der einzige saubere Weg. Schalte
<code>CMD_ALLOW_EMAIL_REGISTER</code> nicht „nur kurz&quot; auf <code>true</code> – eine offene HedgeDoc-Instanz im Netz ist
innerhalb von Tagen ein fremder Datenspeicher.</div>
</div>
<h3 id="schritt-5-die-erste-notiz">Schritt 5: Die erste Notiz</h3>
<p>Nach dem Anmelden legst du mit <strong>New</strong> eine Notiz an. Links tippst du Markdown, rechts steht sofort
das Ergebnis:</p>
<p><figure class="my-6"><img src="/tutorials/hedgedoc-markdown-notizen/hd-editor_hu_93b672d9cc90e586.webp" srcset="/tutorials/hedgedoc-markdown-notizen/hd-editor_hu_2fa8938ef514d275.webp 480w, /tutorials/hedgedoc-markdown-notizen/hd-editor_hu_93b672d9cc90e586.webp 768w, /tutorials/hedgedoc-markdown-notizen/hd-editor_hu_18b9be18d0c155e5.webp 1200w, /tutorials/hedgedoc-markdown-notizen/hd-editor_hu_e4f883599e75e698.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/tutorials/hedgedoc-markdown-notizen/hd-editor_hu_c5df561cd976cf0c.webp"
    alt="Der HedgeDoc-Editor in der geteilten Ansicht: links Markdown-Quelltext mit Tabelle und Aufgabenliste, rechts die formatierte Vorschau." title="Links Quelltext, rechts Ergebnis – Tabellen und Aufgabenlisten funktionieren direkt"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">Links Quelltext, rechts Ergebnis – Tabellen und Aufgabenlisten funktionieren direkt</figcaption></figure></p>
<p>Tabellen, Aufgabenlisten mit anklickbaren Kästchen, Zitate, Codeblöcke, MathJax und ein
Präsentationsmodus sind eingebaut. Wer die Notiz gleichzeitig offen hat, sieht deine Änderungen
sofort – oben rechts steht, wie viele Leute gerade dabei sind.</p>
<h3 id="schritt-6-fünf-stufen-wer-was-darf">Schritt 6: Fünf Stufen, wer was darf</h3>
<p>Oben rechts über der Vorschau steht die aktuelle Berechtigung der Notiz. Ein Klick öffnet die
Auswahl:</p>
<p><figure class="my-6"><img src="/tutorials/hedgedoc-markdown-notizen/hd-permission_hu_faf4ca2be6fe9c22.webp" srcset="/tutorials/hedgedoc-markdown-notizen/hd-permission_hu_b8d37a5ef7fe70dd.webp 480w, /tutorials/hedgedoc-markdown-notizen/hd-permission_hu_faf4ca2be6fe9c22.webp 768w, /tutorials/hedgedoc-markdown-notizen/hd-permission_hu_3becce8f9969ab85.webp 1200w, /tutorials/hedgedoc-markdown-notizen/hd-permission_hu_d6e5873fdcb474f5.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/tutorials/hedgedoc-markdown-notizen/hd-permission_hu_a507155eb8a671e4.webp"
    alt="Das Berechtigungsmenü einer HedgeDoc-Notiz mit den fünf Stufen von Editable bis Private." title="Fünf Stufen – der Zusatz forbid guests ist der entscheidende Unterschied"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">Fünf Stufen – der Zusatz forbid guests ist der entscheidende Unterschied</figcaption></figure></p>
<p>Die Stufen von offen nach zu:</p>
<ul>
<li><strong>Editable</strong> – angemeldete Nutzer dürfen bearbeiten, Gäste dürfen lesen</li>
<li><strong>Limited</strong> – angemeldete Nutzer dürfen bearbeiten, <strong>Gäste bleiben ganz draußen</strong></li>
<li><strong>Locked</strong> – nur der Besitzer bearbeitet, Gäste dürfen lesen</li>
<li><strong>Protected</strong> – nur der Besitzer bearbeitet, Gäste bleiben draußen</li>
<li><strong>Private</strong> – nur der Besitzer sieht die Notiz überhaupt</li>
</ul>
<p>Eine sechste Stufe, <strong>Freely</strong> („Anyone can edit“), bietet HedgeDoc nur an, wenn Gäste mitschreiben
dürfen. Mit <code>CMD_ALLOW_ANONYMOUS: &quot;false&quot;</code> fehlt sie im Menü.</p>
<p>Der Zusatz „forbid guests&quot; ist der Punkt, an dem man sich vertut. Eine Notiz auf <strong>Limited</strong> ist für
Außenstehende auch dann unsichtbar, wenn du sie veröffentlichst – der Aufruf endet mit einer
Weiterleitung. Willst du jemandem ohne Konto etwas zu lesen geben, ist <strong>Locked</strong> die richtige
Stufe. Der Unterschied ist eindeutig: Dieselbe veröffentlichte Notiz liefert einem Gast mit
<code>Limited</code> eine <code>302</code> und mit <code>Locked</code> eine <code>200</code>.</p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-paprika-400 bg-paprika-50 dark:border-paprika-700 dark:bg-paprika-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">🔥</span>Der Schalter, auf den du dich nicht verlassen darfst
  </p>
  <div class="prose-kitchen text-sm"><code>CMD_ALLOW_ANONYMOUS_EDITS</code> klingt so, als könne man damit Gästen das Schreiben verbieten. Das tut
er nicht – er kann es nur <strong>erlauben</strong>. Ab Werk steht er auf <code>false</code> und ist trotzdem wirkungslos,
solange <code>CMD_ALLOW_ANONYMOUS</code> auf <code>true</code> steht: Dann bietet HedgeDoc „Freely&quot; an, und auf einer
solchen Notiz schreibt jeder Browser ohne Anmeldung mit. Setzt du <code>CMD_ALLOW_ANONYMOUS_EDITS</code> bei
unserem <code>CMD_ALLOW_ANONYMOUS: &quot;false&quot;</code> auf <code>true</code>, holst du genau diese Stufe zurück. Lass ihn
deshalb weg. Was Gäste dürfen, entscheidet <strong>allein die Berechtigung der Notiz</strong> – und „Freely&quot;
ist mit unserer Konfiguration gar nicht erst wählbar.</div>
</div>
<h3 id="schritt-7-weitergeben">Schritt 7: Weitergeben</h3>
<p>Über <strong>Publish</strong> bekommt die Notiz eine zweite, aufgeräumte Adresse unter <code>/s/…</code> – ohne Editor, ohne
Werkzeugleiste, nur der fertige Text. Das ist die Fassung, die du verschickst:</p>
<p><figure class="my-6"><img src="/tutorials/hedgedoc-markdown-notizen/hd-publish_hu_553ad9166087c535.webp" srcset="/tutorials/hedgedoc-markdown-notizen/hd-publish_hu_2a304f149197940e.webp 480w, /tutorials/hedgedoc-markdown-notizen/hd-publish_hu_553ad9166087c535.webp 768w, /tutorials/hedgedoc-markdown-notizen/hd-publish_hu_fe1a5e65805a8740.webp 1200w, /tutorials/hedgedoc-markdown-notizen/hd-publish_hu_69a6d29d7cfec6b0.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/tutorials/hedgedoc-markdown-notizen/hd-publish_hu_cebc8c8f905a958b.webp"
    alt="Die veröffentlichte Ansicht einer HedgeDoc-Notiz ohne Editor, mit Tabelle, Aufgabenliste und Zähler für die Aufrufe." title="Die veröffentlichte Fassung – lesbar für alle, die den Link haben"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">Die veröffentlichte Fassung – lesbar für alle, die den Link haben</figcaption></figure></p>
<p>Der Editor bleibt unter der ursprünglichen Adresse erreichbar; die veröffentlichte Fassung zieht bei
jeder Änderung nach. Wer die Notiz nur lesen können soll, bekommt den <code>/s/</code>-Link <strong>und</strong> die Notiz
steht auf <code>Locked</code>.</p>
<h2 id="wenn-es-nicht-funktioniert">Wenn es nicht funktioniert</h2>
<div class="troubleshoot not-prose">
<p><strong>Der Datenbank-Container startet nicht und meldet etwas über <code>pg_ctlcluster</code> und Verzeichnisnamen.</strong> Das ist die PostgreSQL-18-Umstellung: Ab dieser Hauptversion darf der Mount nicht mehr auf <code>/var/lib/postgresql/data</code> zeigen, sondern muss eine Ebene höher auf <code>/var/lib/postgresql</code>. Der Container bricht auch dann ab, wenn das Volume nagelneu und leer ist – die Prüfung hängt am Mount, nicht am Inhalt. Bei einer <strong>bestehenden</strong> Datenbank reicht das Verschieben des Mounts allerdings nicht; dann brauchst du erst einen <code>pg_dump</code> mit der alten Hauptversion und einen Import in die neue.</p>
<p><strong>Nach dem Umstellen des Mounts kommt <code>mkdir: can't create directory '/var/lib/postgresql/18/': Permission denied</code>.</strong> Du benutzt einen Bind-Mount, und das Verzeichnis auf dem Host gehört root – der Postgres-Benutzer im Container darf darin nichts anlegen. Am alten Pfad hat das Image die Rechte noch selbst korrigiert, am neuen nicht mehr. Entweder du gibst das Verzeichnis dem Container-Benutzer, oder du nimmst wie hier ein benanntes Volume, bei dem Docker die Rechte aus dem Image übernimmt.</p>
<p><strong>Verschickte Links zeigen auf <code>localhost</code> oder tragen einen Port im Namen.</strong> HedgeDoc baut Links nicht aus dem <code>Host</code>-Header, sondern aus der Konfiguration. Setz <code>CMD_DOMAIN</code> auf deine Domain (nur den Namen, kein <code>https://</code>, kein Pfad), <code>CMD_PROTOCOL_USESSL</code> auf <code>true</code> und <code>CMD_URL_ADDPORT</code> auf <code>false</code>. Nach einer Änderung muss der Container neu gestartet werden, damit sie greift.</p>
<p><strong>Ein Gast hat deine Notiz bearbeitet, obwohl <code>CMD_ALLOW_ANONYMOUS_EDITS</code> auf <code>false</code> steht.</strong> Diese Option wirkt ausschließlich dann, wenn <code>CMD_ALLOW_ANONYMOUS</code> auf <code>false</code> steht – bei <code>true</code> ist sie wirkungslos. Entscheidend ist die Berechtigung der einzelnen Notiz: Steht sie auf <code>Freely</code>, darf jeder mit dem Link schreiben. Setz <code>CMD_ALLOW_ANONYMOUS</code> auf <code>false</code>, starte den Container neu und stell die Notiz auf <code>Limited</code> oder <code>Locked</code>.</p>
<p><strong>In der Notizliste tauchen Notizen wie <code>wp-json</code> oder <code>xmlrpc.php</code> auf, die niemand angelegt hat.</strong> Das waren Bots, die nach WordPress suchen. Bei <code>CMD_ALLOW_ANONYMOUS: &quot;true&quot;</code> zusammen mit <code>CMD_ALLOW_FREEURL: &quot;true&quot;</code> legt jeder Aufruf einer freien Adresse eine neue Notiz an. Stell <code>CMD_ALLOW_ANONYMOUS</code> auf <code>false</code> und starte den Stack neu. Die bereits angelegten Notizen haben keinen Besitzer, der sie in der Oberfläche löschen könnte; du entfernst sie in der Datenbank: <code>docker compose exec database psql -U hedgedoc -d hedgedoc -c 'DELETE FROM &quot;Notes&quot; WHERE &quot;ownerId&quot; IS NULL;'</code> – ihre Revisionen verschwinden dabei mit.</p>
<p><strong>Ein Außenstehender bekommt beim veröffentlichten Link nur eine Weiterleitung auf die Startseite.</strong> Dann steht die Notiz auf <code>Limited</code> oder <code>Protected</code> – beide Stufen sperren Gäste komplett aus, auch bei veröffentlichten Fassungen. Für „nur lesen, aber für alle&quot; ist <code>Locked</code> die richtige Wahl. <code>CMD_ALLOW_ANONYMOUS</code> hat damit nichts zu tun: Die Option regelt nur, ob Gäste Notizen <strong>anlegen</strong> dürfen, nicht, ob sie welche lesen.</p>

</div>
<p class="mt-3 text-sm text-slate-500 dark:text-slate-400">
  Dein Problem ist nicht dabei?
  <a href="/fehler/" class="font-medium text-paprika-700 hover:underline dark:text-paprika-400">Alle Fehlermeldungen durchsuchen →</a>
</p>

<h2 id="wartung--backups">Wartung &amp; Backups</h2>
<p><strong>Updates</strong> laufen über den Image-Tag. HedgeDoc 1.x veröffentlicht alle paar Wochen eine
Minor-Version; die Datenbankmigration passiert beim Start:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">cd</span> /opt/hedgedoc
</span></span><span class="line"><span class="cl">docker compose <span class="nb">exec</span> -T database pg_dump -U hedgedoc hedgedoc &gt; vor-update-<span class="k">$(</span>date +%F<span class="k">)</span>.sql
</span></span><span class="line"><span class="cl">sed -i <span class="s1">&#39;s/hedgedoc:1.12.0/hedgedoc:NEUE_VERSION/&#39;</span> compose.yaml
</span></span><span class="line"><span class="cl">docker compose pull <span class="o">&amp;&amp;</span> docker compose up -d
</span></span><span class="line"><span class="cl">docker compose logs app --tail <span class="m">10</span></span></span></code></pre></div>
</div>
<p>Der Dump davor ist keine Förmlichkeit – er ist der einzige Weg zurück, wenn eine Migration
schiefgeht. <strong>Die Hauptversion von PostgreSQL fasst du dabei nicht mit an.</strong> Ein Sprung von 18 auf
19 braucht immer Dump und Import; ein bloßes Hochsetzen des Image-Tags endet damit, dass der
Container mit einer Versionsmeldung stehen bleibt.</p>
<p><strong>Sichern</strong> musst du drei Dinge:</p>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">pg_dump-Ausgabe          # Notizen, Konten, Berechtigungen
</span></span><span class="line"><span class="cl">/opt/hedgedoc/uploads/   # hochgeladene Bilder
</span></span><span class="line"><span class="cl">/opt/hedgedoc/.env       # Datenbankpasswort und Session-Secret</span></span></code></pre></div>
</div>
<p>Das <code>.env</code> gehört ausdrücklich dazu: Ohne das <code>CMD_SESSION_SECRET</code> sind nach einer
Wiederherstellung alle Sitzungen ungültig, ohne das Datenbankpasswort kommt die Anwendung nicht an
ihre eigenen Daten. Ein täglicher Dump per systemd-Timer und beides zusammen ins
<a href="/tutorials/backups-mit-restic/">Restic-Backup</a> – mehr braucht es nicht.</p>
<p><strong>Realistischer Aufwand:</strong> wenig. Ein Update pro Quartal, ein Blick auf die Notizliste, wenn du
merkst, dass niemand mehr aufräumt. Der Punkt, den du dir im Kalender notieren solltest, ist die
Postgres-Hauptversion: Solange du beim gepinnten Tag bleibst, passiert nichts – aber irgendwann
läuft der Support aus, und dann ist Dump-und-Import fällig.</p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-herb-400 bg-herb-50 dark:border-herb-700 dark:bg-herb-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">🧑‍🍳</span>Wenn es nur intern sein soll
  </p>
  <div class="prose-kitchen text-sm">Eine HedgeDoc-Instanz, die nur dein Haushalt oder Team braucht, muss nicht im offenen Netz stehen.
Hinter einer IP-Beschränkung oder komplett im VPN sparst du dir jede Diskussion über Registrierung
und Gastzugriff – das Muster steht in
<a href="/tutorials/interne-dienste-nur-im-vpn/">Dienste nur im VPN erreichbar machen</a>.</div>
</div>
]]></content:encoded></item></channel></rss>