https://github.com/gastonmorixe/ferry
⛵️ Self-host your web apps fast and free. No open-ports. Fully automated.
https://github.com/gastonmorixe/ferry
deployment dokku heroku paas self-hosted self-hosting
Last synced: about 13 hours ago
JSON representation
⛵️ Self-host your web apps fast and free. No open-ports. Fully automated.
- Host: GitHub
- URL: https://github.com/gastonmorixe/ferry
- Owner: gastonmorixe
- License: mit
- Created: 2026-03-20T03:56:03.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2026-05-18T00:20:31.000Z (about 1 month ago)
- Last Synced: 2026-05-18T02:29:16.883Z (about 1 month ago)
- Topics: deployment, dokku, heroku, paas, self-hosted, self-hosting
- Language: Shell
- Homepage:
- Size: 10.5 MB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
Ferry
PaaS scaffold for self-hosting web apps for free and zero open ports
Ferry combines 🐳 [Dokku](https://dokku.com) and 🌩️ [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) into a single workflow.
Scaffold a starter app with `ferry new`, or take an existing GitHub repo to a live HTTPS site with automatic 🌎 **DNS**, 🛬 **ingress routing**, and 🔒 **TLS termination** at Cloudflare's edge, so your server IP is **never exposed** while staying **100% free to self-host** even on dynamic residential IPs.
```bash
# Scaffold and deploy in one flow
$ ferry new myapp -t express --deploy -y
# Done. App created and deployed
# Or deploy an existing GitHub repo directly
$ ferry deploy myapp -r owner/repo -H app.example.com -y
# Done. Live at https://app.example.com
```
---
## TOC
- [Features](#features)
- [How It Works](#how-it-works)
- [Quick Start](#quick-start)
- [CLI Reference](#cli-reference)
- [Examples](#examples)
- [Configuration](#configuration)
- [Documentation](#documentation)
- [Development](#development)
- [Project Structure](#project-structure)
- [Requirements](#requirements)
- [License](#license)
---
## Features
- 🛬 **Zero open ports:** No 80, no 443, no public IP. All traffic flows through Cloudflare's encrypted tunnel.
- 🧱 **Built-in app scaffolding:** `ferry new` generates 11 starter app templates: Express, NestJS, Next.js, React, FastAPI, Django, Rails, Go (net/http + Fiber), Rust (Axum + Actix).
- 🧭 **Privacy-aware generated apps:** Templates ship with multi-source IP classification — Tor / VPN / datacenter / mobile / residential — backed by free public datasets (sapics/ip-location-db, X4BNet, Tor Project, PeeringDB). No third-party API keys required.
- 🚀 **One-command deploy:** `ferry deploy` handles app creation, DNS, ingress, tunnel restart, git push, and verification.
- 🧑💻 **Git push deploys:** Standard `git push dokku main:master` workflow, just like Heroku.
- 🌎 **Automatic DNS:** CNAME records created via Cloudflare API for any domain in your account.
- 🔦 **Auto port detection:** Reads `Dockerfile EXPOSE`, framework conventions (Next.js, Nuxt, Remix, Express), or `package.json` scripts.
- 🎛️ **Per-app tuning:** `ferry tune` adjusts container memory limits and (for Node apps) V8 heap size without a redeploy.
- 🔐 **Free TLS:** SSL certificates managed by Cloudflare at the edge.
- 💻 **Interactive TUI:** Arrow-key menu, 256-color palette, spinner animations, graceful degradation to plain text.
- 👩🏼💻 **Fully scriptable:** `-y` flag for CI/CD, pipe-safe output, clean exit codes.
- 🐳 **Docker Compose stack:** Two containers (`cloudflared` + `dokku`), persistent volumes, `restart: unless-stopped`.
---
## How It Works
```
┌──────────────────────────────────────────┐
│ Your Server │
│ │
Internet ──> Cloudflare Edge ───┼──> cloudflared ──> dokku ──> app │
(TLS + CDN) │ (tunnel) (nginx) (container) │
│ │
└──────────────────────────────────────────┘
```
1. Cloudflare receives requests at their edge and terminates TLS
2. Traffic is forwarded through an encrypted QUIC tunnel to the `cloudflared` container
3. `cloudflared` matches the hostname against ingress rules and routes to Dokku's nginx
4. Nginx proxies to the correct app container based on the `Host` header
5. The response flows back through the same path
**Your server never exposes ports 80 or 443.** The only host port is `3022` (SSH for git push), accessible from LAN only.
---
## Quick Start
### 1. Clone Ferry
```bash
git clone https://github.com/gastonmorixe/ferry.git ~/ferry
cd ~/ferry
```
### 2. Configure environment
```bash
cp .env.example .env
# Edit .env: set TUNNEL_ID and DOKKU_HOSTNAME
```
### 3. Start the stack
```bash
docker compose up -d
```
### 4. Set up Cloudflare API access
```bash
ferry login
```
### 5. Create or deploy your first app
```bash
# Fastest path: scaffold and deploy in one command
ferry new myapp -t express --deploy -y
# Or scaffold locally, then deploy later
ferry new myapp -t express
ferry deploy myapp
# Or deploy an existing GitHub repo directly
ferry deploy myapp -r owner/repo -H myapp.example.com
```
See [Initial Setup](docs/initial-setup.md) for the full first-time setup guide including tunnel creation, SSH keys, and DNS configuration. For scaffold-first usage, see [Scaffolding Apps](docs/scaffolding-apps.md). For the broader deployment lifecycle, see [Deploying Apps](docs/deploying-apps.md).
---
## CLI Reference
Run `ferry` with no arguments for an interactive arrow-key menu, or pass a command directly.
### Commands
```
ferry Interactive menu
ferry new [] Create a new app from a template
ferry login Set up Cloudflare API access
ferry deploy [] Deploy a new app
ferry remove [] Remove an app + DNS + ingress
ferry prune reconcile Reconcile orphan ingress rules
ferry status Full system dashboard
ferry list Quick app list
ferry reload Validate config + restart cloudflared
ferry rebuild Rebuild a Dokku app
ferry tune Adjust memory limits + Node heap (no redeploy)
ferry logs Tail app logs
ferry help Show help
```
### Flags
**Global:**
| Flag | Effect |
|---|---|
| `-y` / `--yes` | Skip all confirmations (non-interactive mode) |
| `-h` / `--help` | Show help |
**New:**
| Flag | Effect |
|---|---|
| `-t` / `--template` | Generator template to use (`express`, `nextjs`, `fastapi`, etc.) |
| `-o` / `--output` | Output directory (default: `apps/` or `$FERRY_APPS_DIR/`) |
| `-p` / `--port` | Override the template's default port |
| `--deploy` | Generate and immediately chain into `ferry deploy` |
| `--no-deploy` | Skip the deploy prompt after generation |
| `-l` / `--list` | List available templates and exit |
**Deploy:**
| Flag | Effect |
|---|---|
| `-r` / `--repo` | GitHub repo to clone (`owner/repo` or URL) |
| `-H` / `--hostname` | Set hostname (default: `.$DOKKU_HOSTNAME`) |
| `-p` / `--port` | Set app port (default: auto-detect, fallback: 5000) |
| `-m` / `--memory` | Container memory limit in MB (default: 256) |
| `-b` / `--branch` | Git branch to push (default: auto-detect) |
| `-d` / `--dir` | Local app directory (skip clone) |
| `--no-push` | Set up infrastructure only, skip git push |
**Tune:**
| Flag | Effect |
|---|---|
| `-m` / `--memory` | New container memory limit in MB (min 64) |
| `--heap` | Override Node `--max-old-space-size` (default: memory − 48) |
| `--runtime` | `node` / `python` / `go` / `ruby` / `rust` — enables heap auto-tune for Node |
**Login:**
| Flag | Effect |
|---|---|
| `-t` / `--token` | Pass API token directly (skip interactive prompt) |
---
## Examples
```bash
# List built-in templates
ferry new --list
# Scaffold a new app into apps/myapp
ferry new myapp -t express -y
# Scaffold and immediately deploy
ferry new myapp -t fastapi --deploy -y
# Scaffold into a custom directory
ferry new myapp -t nextjs -o ~/projects/myapp -y
# Deploy from GitHub (clone, detect port, create DNS, push, verify)
ferry deploy myapp -r owner/repo -H app.example.com -y
# Deploy with explicit port
ferry deploy myapp -H app.example.com -p 3000 -y
# Deploy from a local directory
ferry deploy myapp -d ./my-app -y
# Set up infrastructure only (push code later)
ferry deploy myapp -r owner/repo -H app.example.com --no-push -y
# Interactive deploy (prompts for everything)
ferry deploy
# Deploy with a bigger memory limit (Node apps auto-tune their V8 heap)
ferry deploy myapp -r owner/repo -m 512 -y
# Tune an existing app's memory without redeploying
ferry tune myapp -m 512 --runtime node -y
# Remove an app (destroys app, DNS, ingress)
ferry remove myapp -y
# Set up API token non-interactively
ferry login -t "your-api-token" -y
# Pipe-safe: colors auto-disabled when output is not a TTY
ferry status > status.txt
```
### Updating a deployed app
After the initial deploy, updates are just a git push:
```bash
cd apps/myapp
git pull origin main
git push dokku main:master
```
---
## Configuration
### Environment Variables (.env)
| Variable | Required | Description |
|---|---|---|
| `TUNNEL_ID` | Yes | Cloudflare tunnel UUID |
| `DOKKU_HOSTNAME` | Yes | Base domain for default app hostnames |
| `CF_API_TOKEN` | Auto | Cloudflare API token (set via `ferry login`) |
| `CF_ACCOUNT_ID` | Auto | Cloudflare account ID (auto-discovered from token) |
Copy `.env.example` to `.env` to get started.
### DNS Management
With an API token, Ferry creates and deletes DNS records via the Cloudflare API for any domain in your account. Zone IDs are auto-resolved from hostnames, so no manual configuration is needed.
Without an API token, DNS creation falls back to zone-scoped origin certs via `cloudflared tunnel route dns`, limited to domains with a matching cert file in `tunnels/providers/cloudflare/`.
### Scripting and CI/CD
All commands support `-y` for unattended use. The 256-color TUI degrades gracefully to 16-color or plain text when piped. Confirmation prompts auto-decline when stdin is not a TTY, making the script safe in pipelines.
Generated and cloned app sources default to `apps/`. Set `FERRY_APPS_DIR` to redirect that location globally.
---
## Documentation
Detailed guides in [`docs/`](docs/):
| Guide | Description |
|---|---|
| [Deploying Apps](docs/deploying-apps.md) | App lifecycle: deploy, update, scale, configure |
| [Scaffolding Apps](docs/scaffolding-apps.md) | In-depth guide to `ferry new`, templates, output paths, and deploy flows |
| [Deploy Guide: GitHub to Live](docs/deploy-guide-github-to-live.md) | End-to-end walkthrough from repo to live URL |
| [Architecture](docs/architecture.md) | Container topology, networking, DNS, traffic flow |
| [Troubleshooting](docs/troubleshooting.md) | Common problems and solutions |
| [Initial Setup](docs/initial-setup.md) | First-time server setup reference |
| [Cloudflare LLM Docs](docs/cloudflare-llms-index.md) | Cloudflare developer doc links |
---
## Development
Ferry now has a repo-level development workflow for bootstrapping dependencies, linting Bash, and running tests.
### Required dev tools
- `git`
- `make`
- `jq`
- `python3`
- `python3 -m pip` with [requirements-dev.txt](requirements-dev.txt)
- `bats`
- `shellcheck`
### Optional tools
- `docker` for deploy/status/reload/remove flows
- `gh` for `ferry deploy --repo ...`
### Bootstrap and verify
```bash
git submodule update --init --recursive
make bootstrap
make lint
make test
make check
```
What each target does:
- `make bootstrap` initializes submodules and verifies the local dev toolchain.
- `make lint` runs `shellcheck` against Ferry-owned shell code only.
- `make test` runs the unit, integration, and generator Bats suites.
- `make check` runs lint and tests together.
The Bats helper libraries are vendored as git submodules in [test/test_helper](test/test_helper), so a fresh clone must initialize submodules before tests will pass.
CI is defined in [.github/workflows/ci.yml](.github/workflows/ci.yml) and runs the same bootstrap, lint, and test flow on every push and pull request.
---
## Project Structure
```
ferry/
├── ferry.sh # CLI script (bash)
├── docker-compose.yml # cloudflared + dokku services
├── .env # Secrets and config (gitignored)
├── .env.example # Template for .env
├── .gitignore
├── README.md
├── CHANGELOG.md
├── Makefile
├── .gitmodules
├── requirements-dev.txt
├── docs/
│ ├── deploying-apps.md
│ ├── scaffolding-apps.md
│ ├── deploy-guide-github-to-live.md
│ ├── architecture.md
│ ├── troubleshooting.md
│ ├── initial-setup.md
│ └── cloudflare-llms-index.md
├── .github/
│ └── workflows/
│ └── ci.yml
├── scripts/ # Dev bootstrap, lint, and test entry points
├── generators/ # Built-in app generators for ferry new
├── test/ # Bats unit, integration, and generator tests
├── tunnels/
│ └── providers/
│ └── cloudflare/
│ ├── config.yml # Tunnel ingress rules (gitignored)
│ └── *.cert # Zone-scoped origin certs (gitignored)
└── apps/ # App source directories (gitignored)
```
---
## Requirements
| Component | Notes |
|---|---|
| Linux host | Any architecture supported by Docker (x86_64, arm64) |
| Docker + Docker Compose | v29+ / v5+ recommended |
| Cloudflare account | Free tier works (tunnel + DNS) |
| `cloudflared` | On host, for initial tunnel creation only |
| `bash`, `curl`, `jq`, `python3` + PyYAML | Used by the `ferry` script |
| `gh` (GitHub CLI) | Optional, for `--repo` clone support |
---
## Amazing Communities:
- [r/selfhosted](https://www.reddit.com/r/selfhosted/)
- [awesome-selfhosted](https://github.com/awesome-selfhosted/awesome-selfhosted)
---
## TODOs
- [ ] [Other tunnels providers](https://github.com/anderspitman/awesome-tunneling?tab=readme-ov-file) like [Tailscale Funnel](https://tailscale.com/docs/features/tailscale-funnel)
---
## License
MIT License. Copyright (c) 2026 [Gaston Morixe](https://github.com/gastonmorixe).
See [LICENSE](LICENSE) for details.