{"id":51098027,"url":"https://github.com/gastonmorixe/ferry","last_synced_at":"2026-06-24T08:02:23.620Z","repository":{"id":345662502,"uuid":"1186852834","full_name":"gastonmorixe/ferry","owner":"gastonmorixe","description":"⛵️ Self-host your web apps fast and free. No open-ports. Fully automated. ","archived":false,"fork":false,"pushed_at":"2026-05-18T00:20:31.000Z","size":11041,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-18T02:29:16.883Z","etag":null,"topics":["deployment","dokku","heroku","paas","self-hosted","self-hosting"],"latest_commit_sha":null,"homepage":"","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gastonmorixe.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-20T03:56:03.000Z","updated_at":"2026-05-18T00:20:33.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/gastonmorixe/ferry","commit_stats":null,"previous_names":["gastonmorixe/ferry"],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/gastonmorixe/ferry","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gastonmorixe%2Fferry","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gastonmorixe%2Fferry/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gastonmorixe%2Fferry/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gastonmorixe%2Fferry/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gastonmorixe","download_url":"https://codeload.github.com/gastonmorixe/ferry/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gastonmorixe%2Fferry/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34722710,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-24T02:00:07.484Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["deployment","dokku","heroku","paas","self-hosted","self-hosting"],"created_at":"2026-06-24T08:02:21.015Z","updated_at":"2026-06-24T08:02:23.614Z","avatar_url":"https://github.com/gastonmorixe.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/assets/ferry-logo.png?v=2\" alt=\"Ferry\" height=\"300\" /\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003eFerry\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\u003cstrong\u003ePaaS scaffold for self-hosting web apps for free and zero open ports\u003c/strong\u003e\u003c/p\u003e\n\nFerry combines 🐳 [Dokku](https://dokku.com) and 🌩️ [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) into a single workflow.\n\nScaffold 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.\n\n```bash\n# Scaffold and deploy in one flow\n$ ferry new myapp -t express --deploy -y\n# Done. App created and deployed\n\n# Or deploy an existing GitHub repo directly\n$ ferry deploy myapp -r owner/repo -H app.example.com -y\n# Done. Live at https://app.example.com\n```\n\n---\n\n## TOC\n\n- [Features](#features)\n- [How It Works](#how-it-works)\n- [Quick Start](#quick-start)\n- [CLI Reference](#cli-reference)\n- [Examples](#examples)\n- [Configuration](#configuration)\n- [Documentation](#documentation)\n- [Development](#development)\n- [Project Structure](#project-structure)\n- [Requirements](#requirements)\n- [License](#license)\n\n---\n\n## Features\n\n- 🛬 **Zero open ports:** No 80, no 443, no public IP. All traffic flows through Cloudflare's encrypted tunnel.\n- 🧱 **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).\n- 🧭 **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.\n- 🚀 **One-command deploy:** `ferry deploy` handles app creation, DNS, ingress, tunnel restart, git push, and verification.\n- 🧑‍💻 **Git push deploys:** Standard `git push dokku main:master` workflow, just like Heroku.\n- 🌎 **Automatic DNS:** CNAME records created via Cloudflare API for any domain in your account.\n- 🔦 **Auto port detection:** Reads `Dockerfile EXPOSE`, framework conventions (Next.js, Nuxt, Remix, Express), or `package.json` scripts.\n- 🎛️ **Per-app tuning:** `ferry tune` adjusts container memory limits and (for Node apps) V8 heap size without a redeploy.\n- 🔐 **Free TLS:** SSL certificates managed by Cloudflare at the edge.\n- 💻 **Interactive TUI:** Arrow-key menu, 256-color palette, spinner animations, graceful degradation to plain text.\n- 👩🏼‍💻 **Fully scriptable:** `-y` flag for CI/CD, pipe-safe output, clean exit codes.\n- 🐳 **Docker Compose stack:** Two containers (`cloudflared` + `dokku`), persistent volumes, `restart: unless-stopped`.\n\n---\n\n## How It Works\n\n```\n                                ┌──────────────────────────────────────────┐\n                                │               Your Server                │\n                                │                                          │\nInternet ──\u003e Cloudflare Edge ───┼──\u003e cloudflared ──\u003e dokku ──\u003e app         │\n             (TLS + CDN)        │    (tunnel)        (nginx)   (container) │\n                                │                                          │\n                                └──────────────────────────────────────────┘\n```\n\n1. Cloudflare receives requests at their edge and terminates TLS\n2. Traffic is forwarded through an encrypted QUIC tunnel to the `cloudflared` container\n3. `cloudflared` matches the hostname against ingress rules and routes to Dokku's nginx\n4. Nginx proxies to the correct app container based on the `Host` header\n5. The response flows back through the same path\n\n**Your server never exposes ports 80 or 443.** The only host port is `3022` (SSH for git push), accessible from LAN only.\n\n---\n\n## Quick Start\n\n### 1. Clone Ferry\n\n```bash\ngit clone https://github.com/gastonmorixe/ferry.git ~/ferry\ncd ~/ferry\n```\n\n### 2. Configure environment\n\n```bash\ncp .env.example .env\n# Edit .env: set TUNNEL_ID and DOKKU_HOSTNAME\n```\n\n### 3. Start the stack\n\n```bash\ndocker compose up -d\n```\n\n### 4. Set up Cloudflare API access\n\n```bash\nferry login\n```\n\n### 5. Create or deploy your first app\n\n```bash\n# Fastest path: scaffold and deploy in one command\nferry new myapp -t express --deploy -y\n\n# Or scaffold locally, then deploy later\nferry new myapp -t express\nferry deploy myapp\n\n# Or deploy an existing GitHub repo directly\nferry deploy myapp -r owner/repo -H myapp.example.com\n```\n\nSee [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).\n\n---\n\n## CLI Reference\n\nRun `ferry` with no arguments for an interactive arrow-key menu, or pass a command directly.\n\n### Commands\n\n```\nferry                    Interactive menu\nferry new [\u003cname\u003e]       Create a new app from a template\nferry login              Set up Cloudflare API access\nferry deploy [\u003cname\u003e]    Deploy a new app\nferry remove [\u003cname\u003e]    Remove an app + DNS + ingress\nferry prune reconcile    Reconcile orphan ingress rules\nferry status             Full system dashboard\nferry list               Quick app list\nferry reload             Validate config + restart cloudflared\nferry rebuild \u003cname\u003e     Rebuild a Dokku app\nferry tune \u003cname\u003e        Adjust memory limits + Node heap (no redeploy)\nferry logs \u003cname\u003e        Tail app logs\nferry help               Show help\n```\n\n### Flags\n\n**Global:**\n\n| Flag | Effect |\n|---|---|\n| `-y` / `--yes` | Skip all confirmations (non-interactive mode) |\n| `-h` / `--help` | Show help |\n\n**New:**\n\n| Flag | Effect |\n|---|---|\n| `-t` / `--template` | Generator template to use (`express`, `nextjs`, `fastapi`, etc.) |\n| `-o` / `--output` | Output directory (default: `apps/\u003cname\u003e` or `$FERRY_APPS_DIR/\u003cname\u003e`) |\n| `-p` / `--port` | Override the template's default port |\n| `--deploy` | Generate and immediately chain into `ferry deploy` |\n| `--no-deploy` | Skip the deploy prompt after generation |\n| `-l` / `--list` | List available templates and exit |\n\n**Deploy:**\n\n| Flag | Effect |\n|---|---|\n| `-r` / `--repo` | GitHub repo to clone (`owner/repo` or URL) |\n| `-H` / `--hostname` | Set hostname (default: `\u003cname\u003e.$DOKKU_HOSTNAME`) |\n| `-p` / `--port` | Set app port (default: auto-detect, fallback: 5000) |\n| `-m` / `--memory` | Container memory limit in MB (default: 256) |\n| `-b` / `--branch` | Git branch to push (default: auto-detect) |\n| `-d` / `--dir` | Local app directory (skip clone) |\n| `--no-push` | Set up infrastructure only, skip git push |\n\n**Tune:**\n\n| Flag | Effect |\n|---|---|\n| `-m` / `--memory` | New container memory limit in MB (min 64) |\n| `--heap` | Override Node `--max-old-space-size` (default: memory − 48) |\n| `--runtime` | `node` / `python` / `go` / `ruby` / `rust` — enables heap auto-tune for Node |\n\n**Login:**\n\n| Flag | Effect |\n|---|---|\n| `-t` / `--token` | Pass API token directly (skip interactive prompt) |\n\n---\n\n## Examples\n\n```bash\n# List built-in templates\nferry new --list\n\n# Scaffold a new app into apps/myapp\nferry new myapp -t express -y\n\n# Scaffold and immediately deploy\nferry new myapp -t fastapi --deploy -y\n\n# Scaffold into a custom directory\nferry new myapp -t nextjs -o ~/projects/myapp -y\n\n# Deploy from GitHub (clone, detect port, create DNS, push, verify)\nferry deploy myapp -r owner/repo -H app.example.com -y\n\n# Deploy with explicit port\nferry deploy myapp -H app.example.com -p 3000 -y\n\n# Deploy from a local directory\nferry deploy myapp -d ./my-app -y\n\n# Set up infrastructure only (push code later)\nferry deploy myapp -r owner/repo -H app.example.com --no-push -y\n\n# Interactive deploy (prompts for everything)\nferry deploy\n\n# Deploy with a bigger memory limit (Node apps auto-tune their V8 heap)\nferry deploy myapp -r owner/repo -m 512 -y\n\n# Tune an existing app's memory without redeploying\nferry tune myapp -m 512 --runtime node -y\n\n# Remove an app (destroys app, DNS, ingress)\nferry remove myapp -y\n\n# Set up API token non-interactively\nferry login -t \"your-api-token\" -y\n\n# Pipe-safe: colors auto-disabled when output is not a TTY\nferry status \u003e status.txt\n```\n\n### Updating a deployed app\n\nAfter the initial deploy, updates are just a git push:\n\n```bash\ncd apps/myapp\ngit pull origin main\ngit push dokku main:master\n```\n\n---\n\n## Configuration\n\n### Environment Variables (.env)\n\n| Variable | Required | Description |\n|---|---|---|\n| `TUNNEL_ID` | Yes | Cloudflare tunnel UUID |\n| `DOKKU_HOSTNAME` | Yes | Base domain for default app hostnames |\n| `CF_API_TOKEN` | Auto | Cloudflare API token (set via `ferry login`) |\n| `CF_ACCOUNT_ID` | Auto | Cloudflare account ID (auto-discovered from token) |\n\nCopy `.env.example` to `.env` to get started.\n\n### DNS Management\n\nWith 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.\n\nWithout 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/`.\n\n### Scripting and CI/CD\n\nAll 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.\n\nGenerated and cloned app sources default to `apps/\u003cname\u003e`. Set `FERRY_APPS_DIR` to redirect that location globally.\n\n---\n\n## Documentation\n\nDetailed guides in [`docs/`](docs/):\n\n| Guide | Description |\n|---|---|\n| [Deploying Apps](docs/deploying-apps.md) | App lifecycle: deploy, update, scale, configure |\n| [Scaffolding Apps](docs/scaffolding-apps.md) | In-depth guide to `ferry new`, templates, output paths, and deploy flows |\n| [Deploy Guide: GitHub to Live](docs/deploy-guide-github-to-live.md) | End-to-end walkthrough from repo to live URL |\n| [Architecture](docs/architecture.md) | Container topology, networking, DNS, traffic flow |\n| [Troubleshooting](docs/troubleshooting.md) | Common problems and solutions |\n| [Initial Setup](docs/initial-setup.md) | First-time server setup reference |\n| [Cloudflare LLM Docs](docs/cloudflare-llms-index.md) | Cloudflare developer doc links |\n\n---\n\n## Development\n\nFerry now has a repo-level development workflow for bootstrapping dependencies, linting Bash, and running tests.\n\n### Required dev tools\n\n- `git`\n- `make`\n- `jq`\n- `python3`\n- `python3 -m pip` with [requirements-dev.txt](requirements-dev.txt)\n- `bats`\n- `shellcheck`\n\n### Optional tools\n\n- `docker` for deploy/status/reload/remove flows\n- `gh` for `ferry deploy --repo ...`\n\n### Bootstrap and verify\n\n```bash\ngit submodule update --init --recursive\nmake bootstrap\nmake lint\nmake test\nmake check\n```\n\nWhat each target does:\n\n- `make bootstrap` initializes submodules and verifies the local dev toolchain.\n- `make lint` runs `shellcheck` against Ferry-owned shell code only.\n- `make test` runs the unit, integration, and generator Bats suites.\n- `make check` runs lint and tests together.\n\nThe 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.\n\nCI 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.\n\n---\n\n## Project Structure\n\n```\nferry/\n├── ferry.sh                        # CLI script (bash)\n├── docker-compose.yml              # cloudflared + dokku services\n├── .env                            # Secrets and config (gitignored)\n├── .env.example                    # Template for .env\n├── .gitignore\n├── README.md\n├── CHANGELOG.md\n├── Makefile\n├── .gitmodules\n├── requirements-dev.txt\n├── docs/\n│   ├── deploying-apps.md\n│   ├── scaffolding-apps.md\n│   ├── deploy-guide-github-to-live.md\n│   ├── architecture.md\n│   ├── troubleshooting.md\n│   ├── initial-setup.md\n│   └── cloudflare-llms-index.md\n├── .github/\n│   └── workflows/\n│       └── ci.yml\n├── scripts/                        # Dev bootstrap, lint, and test entry points\n├── generators/                     # Built-in app generators for ferry new\n├── test/                           # Bats unit, integration, and generator tests\n├── tunnels/\n│   └── providers/\n│       └── cloudflare/\n│           ├── config.yml          # Tunnel ingress rules (gitignored)\n│           └── *.cert              # Zone-scoped origin certs (gitignored)\n└── apps/                           # App source directories (gitignored)\n```\n\n---\n\n## Requirements\n\n| Component | Notes |\n|---|---|\n| Linux host | Any architecture supported by Docker (x86_64, arm64) |\n| Docker + Docker Compose | v29+ / v5+ recommended |\n| Cloudflare account | Free tier works (tunnel + DNS) |\n| `cloudflared` | On host, for initial tunnel creation only |\n| `bash`, `curl`, `jq`, `python3` + PyYAML | Used by the `ferry` script |\n| `gh` (GitHub CLI) | Optional, for `--repo` clone support |\n\n---\n\n## Amazing Communities:\n\n- [r/selfhosted](https://www.reddit.com/r/selfhosted/) \n- [awesome-selfhosted](https://github.com/awesome-selfhosted/awesome-selfhosted)\n\n---\n\n## TODOs\n- [ ] [Other tunnels providers](https://github.com/anderspitman/awesome-tunneling?tab=readme-ov-file) like [Tailscale Funnel](https://tailscale.com/docs/features/tailscale-funnel)\n\n---\n\n## License\n\nMIT License. Copyright (c) 2026 [Gaston Morixe](https://github.com/gastonmorixe).\n\nSee [LICENSE](LICENSE) for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgastonmorixe%2Fferry","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgastonmorixe%2Fferry","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgastonmorixe%2Fferry/lists"}