From install to live blog.

Practical guides for self-hosting Pressbin. For engine internals and development, see the GitHub README.

Install

Pressbin ships as a single binary. The install script downloads the latest release, runs pressbin setup, and creates config.yml, SQLite database, assets directory, and API keys under your install home.

curl -fsSL https://raw.githubusercontent.com/pressbin/pressbin/main/scripts/install.sh \
  | bash -s -- --site-url https://blog.example.com

Default install location: ~/.pressbin/

Custom install directory

Use --home when the binary should live next to your site (common on shared hosting):

curl -fsSL https://raw.githubusercontent.com/pressbin/pressbin/main/scripts/install.sh \
  | bash -s -- \
    --site-url https://blog.example.com \
    --home ~/domains/blog.example.com/.pressbin

Add the binary to your PATH. pressbin serve loads …/.pressbin/config.yml automatically from the binary path — no extra env vars.

export PATH="$HOME/domains/blog.example.com/.pressbin/bin:$PATH"
pressbin serve

Or pass config explicitly: pressbin serve --config ~/path/.pressbin/config.yml

Run the server

Before serving, preflight checks that assets.path is writable and that admin and sync API keys exist. Run pressbin check to validate without starting HTTP.

pressbin check
pressbin serve

For production, bind Pressbin to 127.0.0.1 (default in config.yml) and put a reverse proxy in front for TLS on port 443. See reverse proxy examples below.

Reverse proxy (TLS)

Pressbin listens on 127.0.0.1:8080 by default. Terminate HTTPS at nginx, Caddy, Apache, or Traefik and forward all traffic to the binary. Set site.url (or PRESSBIN_SITE_URL) to your public https:// URL so RSS and links are correct.

# config.yml (or env)
server:
  host: 127.0.0.1
  port: 8080
site:
  url: https://blog.example.com

GET /api/version and GitHub sync must reach the same public URL you put in PRESSBIN_URL. Admin and sync API routes go through the proxy like everything else.

Nginx

HTTP → Pressbin. Add Certbot or your CA for TLS on listen 443 ssl.

server {
    listen 80;
    server_name blog.example.com;

    location / {
        proxy_pass         http://127.0.0.1:8080;
        proxy_http_version 1.1;
        proxy_set_header   Host              $host;
        proxy_set_header   X-Real-IP         $remote_addr;
        proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
    }

    # Optional: serve synced images from disk (faster than the Go process)
    # location /assets/ {
    #     alias /home/user/.pressbin/assets/;
    #     expires 7d;
    #     add_header Cache-Control "public";
    # }
}

Caddy

Automatic HTTPS when DNS points at this host.

blog.example.com {
    reverse_proxy 127.0.0.1:8080
}

Apache

Enable proxy, proxy_http, and ssl (e.g. a2enmod proxy proxy_http ssl on Debian).

<VirtualHost *:443>
    ServerName blog.example.com

    SSLEngine on
    # SSLCertificateFile / SSLCertificateKeyFile …

    ProxyPreserveHost On
    ProxyPass        / http://127.0.0.1:8080/
    ProxyPassReverse / http://127.0.0.1:8080/
</VirtualHost>

Traefik

File provider example (Pressbin on the same machine as Traefik):

# dynamic/pressbin.yml
http:
  routers:
    pressbin:
      rule: Host(`blog.example.com`)
      entryPoints:
        - websecure
      service: pressbin
      tls:
        certResolver: letsencrypt
  services:
    pressbin:
      loadBalancer:
        servers:
          - url: http://127.0.0.1:8080

With Docker, point the service URL at the container IP or use host.docker.internal:8080 when Pressbin runs on the host.

Content repository

Pressbin separates engine (your VPS) from content (a GitHub repo). Start from the blog template:

your-blog/
├── posts/              # Markdown posts
├── assets/images/      # synced images
└── .github/workflows/sync.yml

Tags, titles, and dates live in YAML front matter per post. There is no central categories file — use tags: [go, tutorial] in each post.

GitHub sync

Add two secrets to your content repo (Settings → Secrets → Actions):

SecretValue
PRESSBIN_URL Your public blog URL (same as --site-url)
PRESSBIN_KEY Contents of sync.key (pb_sync_… only)

On push to main when posts/ or assets/images/ changes:

  1. Actions calls GET {PRESSBIN_URL}/api/version on your instance
  2. Downloads matching pressbin-sync-linux-amd64 from GitHub Releases (checksum verified)
  3. Runs pressbin-sync run — incremental sync from the latest git commit

First push uploads all posts and images. Later pushes delete removed files and upsert changes. Upgrade the server binary when you want new sync behavior — blog workflows stay thin.

Manual full sync

To re-upload every post and image (after migrations, server restore, or drift repair):

  • GitHub: Actions → “Sync content to Pressbin” → Run workflow
  • Local: pressbin-sync run --all /path/to/content-repo

Full sync upserts all files; it does not remove posts deleted from git only. Use normal push sync for deletions, or clean up via the admin API.

Writing posts

---
title: Hello World
date: 2026-05-26
tags: [go, tutorial]
summary: Short blurb for the index page.
status: published
slug: hello-world
---

# Hello World

Body in Markdown.

Use root-absolute image paths in Markdown: /assets/images/hero.svg. Avoid raw.githubusercontent.com in published posts.

status: draft hides a post from the public index and post URL.

Configuration

Config file: config.yml next to your install tree, or PRESSBIN_* environment variables (env overrides YAML). See the config example.

VariablePurpose
PRESSBIN_ASSETS_PATHWritable directory for synced images (/assets/*)
PRESSBIN_DATABASE_PATHSQLite file path
PRESSBIN_SITE_URLPublic URL (RSS, links)
PRESSBIN_SERVER_HOST / PORTListen address

assets.path is required for image sync and must not overlap the database or binary directories.

API keys

Two key types, strict separation:

  • Admin (pb_admin_…) — /api/admin/*, manage posts and keys. Cannot sync.
  • Sync (pb_sync_…) — /api/sync* from GitHub Actions. Cannot access admin.

Created once by pressbin setup as admin.key and sync.key. Create additional sync keys with POST /api/admin/keys and {"label":"ci","type":"sync"}.

HTTP API

Public routes: index, posts, tags, search, RSS at /feed.xml.

Authenticated sync (Bearer sync key):

  • POST /api/sync — create or update post
  • DELETE /api/sync/{slug} — remove post
  • POST /api/sync/asset — upload image or asset
  • DELETE /api/sync/asset/{path} — remove asset

GET /api/version is public — returns {"version":"v1.x.x"} for CI client matching.

Full admin reference: README → Admin API · Bruno collection in the engine repo for interactive testing.

Need the binary?

Install script or download from GitHub Releases.

Install Pressbin