What is freeholdy
freeholdy is a single-VPS orchestrator for your Docker apps. It puts
every project behind nginx + Let's Encrypt and serves each one at its own
{name}.your_domain.com subdomain โ with HTTPS wired up automatically.
You push code (a folder, a git URL, or a pre-packaged plugin); freeholdy auto-detects
whether it's a single-container Dockerfile project or a multi-container
docker-compose.yml stack (compose wins when both are present), builds it,
runs it, and wires up the reverse proxy and SSL certificate. No per-project nginx or
certbot configuration to write.
There are three ways to drive it:
- CLI โ
fhcli, a small command-line client (used in the examples below). - Web UI โ a control panel at
ui.<your-domain>covering the same operations in the browser. - REST API โ everything above is a thin client over the API at
api.<your-domain>, authenticated with a bearer token.
Installation
Prerequisites
- A fresh Ubuntu VPS with root access. The installer handles all packages itself.
- A base domain with a wildcard DNS record (
*.your_domain.com) pointing at the server โ projects are served at{name}.your_domain.comand the API atapi.your_domain.com.
One-command bootstrap
bash <(curl -fsSL https://raw.githubusercontent.com/aafanasev-dev/freeholdy/main/install.sh)
On a server that already runs other apps, clone the repo and run
sudo bash install.sh instead. The installer auto-detects one of two modes
and asks you to confirm before touching anything:
- FRESH โ docker and/or nginx are missing โ it installs what's missing. For a dedicated or empty VPS.
- COEXIST โ both are already present โ it never installs, restarts, or upgrades docker or nginx, and aborts early if your existing nginx config is broken. Safe next to other apps.
The installer then:
- Prompts for a service user (default
freeholdy), your base domain, and a Let's Encrypt email. - Sets up the service user with docker access and passwordless nginx/certbot sudo.
- Creates the Python venv, writes
.env, and picks a free local API port. - Adds the
api.<domain>nginx vhost, obtains its SSL certificate, and installs a nightly renewal cron. - Installs and starts the
freeholdysystemd service. - Prints your first API token โ shown once; save it.
Flags: -u USER (service user), -y (assume yes),
-r (redo every step). Progress is tracked in install.log, so
re-running is idempotent โ it skips finished steps and can, for example, re-enable SSL
after DNS propagates.
Set up the CLI
On your workstation (or the server), from a clone of the repo:
cd cli/
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # set TOKEN and BASE_DOMAIN
Check it works with ./fhcli.py health.
Deploying apps
There is no separate "create project" step โ the first deploy creates the project, and re-running the same deploy redeploys it. Every deploy streams its build log live to your terminal.
From a folder
fhcli deploy myapp ./myapp
Uploads the folder, auto-detects the manifest โ a Dockerfile (it must
EXPOSE a port) or a docker-compose.yml โ builds it, runs it,
and wires up nginx + SSL at myapp.your_domain.com. Compose stacks get one
subdomain per exposed service ({service}.myapp.your_domain.com). Re-run
the command to redeploy.
From git
fhcli deploy mysite https://github.com/owner/repo.git
fhcli deploy mysite git@github.com:owner/repo.git --branch dev
Clones the repo on the server and runs the exact same detect โ build โ run โ nginx pipeline. Re-run to redeploy the latest commit.
For private repos, run fhcli get-git-key โ it prints the
server's SSH public key (generated on first use); add it as a deploy key on the repo,
then deploy over the git@โฆ URL.
From plugins
fhcli plugins # list available plugins
fhcli plugin-add nextcloud mycloud # install one as project "mycloud"
Plugins are pre-packaged apps that deploy through the same pipeline. Some are
interactive: the install prompts you right in the terminal (e.g. to
choose an admin account) before the build starts. If your terminal disconnects
mid-install, re-running plugin-add resumes it.
Plugin catalog
โ๏ธ nextcloud interactive
Nextcloud file sync & share at nextcloud.<domain> (app, postgres, redis, cron). The install prompts for the admin account.
๐ฎ mailserver interactive
Full e-mail server (docker-mailserver) โ SMTP, IMAP, and DKIM at mail.<domain>. Choose addons and the first mailbox during install.
โ๏ธ snappymail interactive
SnappyMail webmail โ a web UI for your mail server at mailui.<domain>. Pairs with the mailserver plugin.
๐ฅ jitsi-meet interactive ยท UDP 10000
Jitsi Meet video conferencing at meet.<domain>. Choose the moderator account during install; UDP 10000 must be reachable from the internet.
๐ผ๏ธ imgstore
Token-gated image storage โ upload via a web UI, share via public links with custom names or auto sha256 URLs.
๐ sftp-go interactive
Personal SFTPGo file server โ SFTP, WebDAV & WebClient over your own folder.
๐ก๏ธ amneziavpn interactive ยท UDP port
AmneziaWG (DPI-resistant WireGuard) VPN server on a raw UDP port โ no subdomain, no SSL. Pick the port, client DNS, and first client during install; your firewall must allow the UDP port in.
๐ outline interactive ยท 2 ports
Outline Server (Shadowbox) โ self-hosted Shadowsocks VPN managed from the Outline Manager desktop app. Pick the API and access-key ports during install; your firewall must allow both in.
๐ freeholdy-help
This guide โ a static page that also doubles as a smoke test for a fresh install.
Versions & rollback
Every deploy is blue/green versioned: the new version is built and verified running before traffic switches to it, so a failed build never takes the live site down. Previous versions are kept for rollback.
fhcli versions myapp # list versions: active / inactive / archived
fhcli rollback myapp 2 # make version 2 live again
fhcli set-backup-limit myapp 3 # keep at most 3 archived versions
- active โ the running version nginx points at.
- inactive โ the previous version, kept as a stopped container for instant rollback (Dockerfile projects only).
- archived โ older versions with their image retained, capped by the backup limit (default 5, oldest pruned first).
AI integration
freeholdy ships a Claude Code skill, fhdeploy, that lets
Claude deploy to your server for you โ no fhcli required. It talks to the
REST API directly: uploads a folder (or points the server at a git URL), follows the
build, and reports the live endpoints.
Setup
- Copy
skills/fhdeploy/from the freeholdy repo into your Claude Code skills directory (e.g.~/.claude/skills/). - Run it from any Claude Code session:
/fhdeploy myapp ./myapp /fhdeploy mysite https://github.com/owner/repo.git - On first use it asks for your API token (printed by the installer) and base domain, and remembers them in Claude Code memory for next time.
Beyond deploys, the skill knows the versioning API โ it can list a project's versions and roll back โ and the git deploy-key flow for private repos. In practice: tell Claude to build something, then tell it to ship it.