FerryDocs
Guides

Production checklist

Prepare a Ferry server for real traffic, from the firewall to backups, updates and monitoring.

Edit on GitHub

Do these steps on a new server before you send real traffic to it. Each step has its command. The commands run on the server, unless the text says "on your machine".

Terminal
curl -fsSL https://github.com/rogerbentcha/ferry/releases/latest/download/install.sh | sudo sh -s -- --base-domain apps.example.com --acme-email [email protected]

This command installs Ferry as a service, with your domain and HTTPS. Prepare the server first. Then do the other steps of the page.

Prepare the server and the firewall

ItemDetail
A Linux serverA is fine. It must have a public . The install script works on Ubuntu 22.04, 24.04 and 26.04, and on Debian 12 and 13, with an or an CPU.
Three open ports22 for SSH, 80 for HTTP and 443 for HTTPS.
A domainYou must control its . This page uses apps.example.com for the apps.

Open ports 22, 80 and 443 in the of the server, or in the firewall of your cloud provider. Keep each other port closed. Ferry binds the and the ports of the datastores to 127.0.0.1. Thus they are never public. You reach them over SSH.

Create a at your DNS provider:

RecordTypeValue
*.apps.example.comA (and AAAA for IPv6)The IP address of the server

Each web service and each static site gets <name>.apps.example.com. A new service needs no new record.

Run ferryd as a service

Run the install command above on the server. The does these changes:

ItemChange
Docker, gitIt installs them if the server does not have them.
User ferryA in the group docker. It runs only ferryd.
/var/lib/ferryThe , with mode 700.
/etc/ferry/ferryd.envThe settings of ferryd, with mode 600.
/etc/systemd/system/ferryd.serviceThe . starts at each boot, and again each time it stops.

To change a setting, add a line FERRY_…=value to /etc/ferry/ferryd.env. Server options gives the variable of each option. Then restart ferryd:

Terminal
sudo systemctl restart ferryd

Read the log of ferryd in the :

Terminal
sudo journalctl -u ferryd -n 30
Output (excerpt)
  Ferry 0.1.0 is running
  Dashboard + API : http://127.0.0.1:7878
  Apps            : https://<name>.apps.example.com
  Limits          : 512 MiB / 1 CPU per container (default), 1024 pids, logs rotated at 10 MiB × 3
  Free disk check : deploys need 1 GiB free

  Create your account: http://127.0.0.1:7878/setup?code=5b0e…

Create the account immediately

Create the account before you do more

Until the account exists, each person who can read the setup link can create it.

On your machine, open an to the server:

Terminal
ssh -N -L 7878:127.0.0.1:7878 you@your-server

Open the Create your account link in your browser. Enter your email and a password of 8 characters or more. If you lost the link, sudo ferryd status --data-dir /var/lib/ferry prints it again. Then connect the CLI with ferry login --server http://127.0.0.1:7878.

Turn on HTTPS

The options --base-domain and --acme-email of the install script write these lines to /etc/ferry/ferryd.env:

/etc/ferry/ferryd.env
FERRY_BASE_DOMAIN=apps.example.com
FERRY_PROXY_ADDR=0.0.0.0:80
FERRY_ACME_EMAIL=[email protected]
FERRY_HTTPS_ADDR=0.0.0.0:443

If you ran the install script without these options, run it again with them. You can also add the lines yourself, then restart ferryd.

Ferry gets one from for each hostname, through the . You need no DNS API and no wildcard certificate. Deploy a test service to make sure that HTTPS works:

Terminal
ferry create hello --image nginx:alpine --port 80 --follow
curl -I https://hello.apps.example.com

A new hostname gets its certificate some seconds after the proxy routes it. Until then, HTTPS answers with a certificate. ferry certificates shows the state of each certificate. Domains and HTTPS tells you how to add your own domains.

Put the dashboard on its own domain

GitHub webhooks and come from the internet. They cannot use your SSH tunnel. For them, the proxy can serve the dashboard and the API on their own hostname, with HTTPS:

/etc/ferry/ferryd.env
FERRY_DASHBOARD_HOST=ferry.apps.example.com

Restart ferryd. The dashboard is then at https://ferry.apps.example.com. The wildcard record already covers this name. Each API call still needs an API token or a signed-in browser.

Set the only when HTTPS is on. Do not change --api-addr to 0.0.0.0 for this. Then the tokens go over the network without encryption.

Protect the access

  • Keep the account ferry for ferryd only. The group docker has the same power as on the server.
  • Keep the modes of the data directory. Ferry creates it with mode 0700, and its secret files with mode 0600. It holds the values of your variables and the passwords of your datastores.
  • Give each tool its own token. In the dashboard, open Server → Account → API tokens. Click New token. Give a name and an expiry. Do not share the .

The password of the account and each give full control of each app on the server. If a secret leaks, replace it:

SecretWhat to do
An API tokenRevoke it under Server → Account. It stops immediately.
The server tokenStop ferryd, delete /var/lib/ferry/api_token and start ferryd again. It makes a new token.
The passwordChange it under Server → Account. Ferry signs out each other browser.
A deploy hookRun ferry deploy-hook rotate NAME.

Back up the data

Ferry makes no backup for you. Three places hold your data. Keep the backups off the server, and encrypt them: they contain each secret of your apps.

The data directory

Terminal
sudo sqlite3 /var/lib/ferry/ferry.db ".backup /backup/ferry.db"

This command copies ferry.db, the database, while ferryd runs. It holds the services, the variables, the deploy history and the datastore passwords. Then copy the rest of the directory: api_token, instance_id and certs/. As an alternative, stop ferryd and copy the full directory.

The databases

Terminal
docker exec ferry-ds-app-db pg_dump -U app_db app_db > app-db.sql

The command runs in the container of the Postgres datastore app-db. app_db is the user and the database. Run it on a schedule, for example from a cron entry. Redis saves its data to its ferry-ds-<name>-data. If you need this data, copy the volume.

The disks

Terminal
docker run --rm -v ferry-svc-srv-…-disk:/data:ro -v "$PWD":/backup alpine tar czf /backup/disk.tgz -C /data .

The data of each is in a volume ferry-svc-<service id>-disk. This command copies it into disk.tgz with a temporary container.

Update Ferry

Run the install command again:

Terminal
curl -fsSL https://github.com/rogerbentcha/ferry/releases/latest/download/install.sh | sudo sh

The script installs the latest release and restarts ferryd. It keeps /var/lib/ferry and /etc/ferry/ferryd.env: your apps, your data and your settings stay. If the release did not change, ferryd continues to run.

Your containers continue to run during the restart. But the proxy is part of ferryd, thus no public traffic gets to them until ferryd is back. A deploy in progress at the moment of the restart fails. Update when no deploy is in progress.

Without the script, replace the two binaries with sudo install -m 755 ferryd ferry /usr/local/bin/. Then run sudo systemctl restart ferryd.

Watch the disk space

Before a build or an image pull, Ferry makes sure that the disk has 1 GiB free. If it has less, the deploy fails early:

Deploy log (excerpt)
10:42:07 ==> Build failed: not enough free disk space on /var/lib/ferry: 812 MiB free, Ferry needs at least 1 GiB (free some space, e.g. remove unused Docker images with `docker image prune`, or change the threshold with `ferryd --min-free-disk`, 0 = off)

Ferry sets no disk quotas. Volumes, images and the can still fill the disk. Ferry deletes its own old images above 5 for each service (--keep-images). Remove the other images and the build cache from time to time:

Terminal
docker image prune
docker builder prune

Ferry also rotates the log of each container at 10 MiB, and keeps 3 files (--log-max-size, --log-max-files). Thus a log cannot fill the disk.

Monitor the server

In the dashboard, open Server → Monitoring. It shows the CPU, the memory, the disk and the network of the server. It also shows each container with its CPU and its memory, and charts of the last 24 hours. The Requests charts show the errors and the of your services.

Follow the log of ferryd:

Terminal
sudo journalctl -u ferryd -f

Each container has a memory limit: 512 MiB by default. When an app goes above it, the kernel stops the container, and Docker starts it again. ferryd writes each of these stops in its log:

Terminal
sudo journalctl -u ferryd | grep 'ran out of memory'

If an app needs more memory, give it its own limit with ferry update NAME --memory 1G. See Scaling and limits.

If you use , it can collect the same numbers at /metrics. Create an API token named prometheus, and put it in a file on the machine of Prometheus. Then add this job:

prometheus.yml
scrape_configs:
  - job_name: ferry
    authorization:
      credentials_file: /etc/prometheus/ferry-token
    static_configs:
      - targets: ['127.0.0.1:7878']

For example, this expression gives an alert when the disk of the data is 90% full: ferry_host_disk_used_bytes / ferry_host_disk_total_bytes > 0.9.

On this page