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):
| Secret | Value |
|---|---|
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:
- Actions calls
GET {PRESSBIN_URL}/api/versionon your instance - Downloads matching
pressbin-sync-linux-amd64from GitHub Releases (checksum verified) - 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.
| Variable | Purpose |
|---|---|
PRESSBIN_ASSETS_PATH | Writable directory for synced images (/assets/*) |
PRESSBIN_DATABASE_PATH | SQLite file path |
PRESSBIN_SITE_URL | Public URL (RSS, links) |
PRESSBIN_SERVER_HOST / PORT | Listen 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 postDELETE /api/sync/{slug}— remove postPOST /api/sync/asset— upload image or assetDELETE /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.