Documentation

DeployLane docs

Everything you need to run your apps on your own Hetzner servers with DeployLane.

Getting started

DeployLane runs your apps on servers in your own Hetzner Cloud project. You'll need:

  • a Hetzner Cloud account with a project for DeployLane;
  • a GitHub repository with a Dockerfile or a docker-compose.yml in its root;
  • a GitHub personal access token if the repository is private;
  • optionally, a domain name you can add DNS records to.

Your account

Accounts are invite-only for now. Contact us and we'll set yours up. During onboarding we link two things from your Hetzner project to your account:

  • a Hetzner Cloud API token with read & write access (Hetzner Console → your project → Security → API tokens);
  • a Hetzner Object Storage key pair, needed for backups, blob storage and Umami (Security → S3 credentials).

Settings → Hetzner Cloud shows which project is linked. Only an admin can change it, so contact us to switch projects or rotate a token.

Your first deploy

  1. Sign in at app.deploylane.dev.
  2. Connect GitHub: Settings → GitHub, paste a personal access token with Contents: read access, and click Connect. Public repositories work without a token.
  3. Create a server: Servers → New server (see Servers).
  4. Create an app on it: Apps → New app, then click Deploy (see Apps).

Tip: press Ctrl K (⌘ K on a Mac) in the dashboard to jump anywhere.

Servers

Servers → New server walks you through Name, Size, Location and Review, then Provision server. In the Review step you pick an existing SSH key or generate a new one. DeployLane then creates in your Hetzner project:

  • an Ubuntu 24.04 server with Docker, ufw, fail2ban and unattended security upgrades;
  • Traefik as the reverse proxy, with automatic Let's Encrypt certificates and HTTP-to-HTTPS redirects;
  • a Hetzner Cloud firewall that allows SSH (22), HTTP (80) and HTTPS (443);
  • the SSH key DeployLane uses to manage the server.

Provisioning usually takes a few minutes. Server types and locations come straight from Hetzner. Hetzner also offers locations in the US and Singapore; choose Falkenstein, Nuremberg or Helsinki to keep your data in the EU.

The server page

  • Overview shows CPU, memory, disk and requests over the last 24 hours, read live from the server.
  • Firewall edits the Hetzner Cloud firewall. Saving replaces the whole rule set. Keep port 22 open: DeployLane manages your server over SSH.
  • OS updates lists pending updates. Install the security updates or all updates, then reboot when it suits you; DeployLane never reboots on its own. You also get a weekly email when a server needs updates or a reboot.
  • Traefik shows routers, services and certificates. You can restart Traefik there or prune certificates that no app uses anymore (this briefly interrupts all apps on the server).
  • Traffic shows requests for the last hour, 24 hours or 7 days, plus a live log, from Traefik's access log.
  • Maintenance holds the built-in jobs: a nightly Docker cleanup at 02:00 (old build cache, dangling images and containers stopped for over 24 hours; never volumes), a daily access log rotation that keeps 14 days, and a weekly disk usage report. They are set up the first time you open the tab, and you can change, run or switch them off there.
  • Danger zone → Destroy this server deletes the server, its firewall and its SSH key (if no other server uses it) from Hetzner, and removes its apps from DeployLane. Backups in Object Storage are kept.

Terminal and SSH keys

The Terminal page opens a root shell on a server, a shell in an app's directory, or a shell inside a container, right in your browser. You can have 3 sessions open at a time; idle sessions close after 15 minutes. Under SSH keys you generate an ed25519 key or import an existing one. Downloads of a private key are logged.

Apps

Apps → New app asks for the target server, an app name (lowercase letters, digits and dashes), the GitHub repo as owner/repo, the branch (default main) and optionally a public domain. Creating an app doesn't deploy it yet: click Deploy when you're ready.

Dockerfile or Docker Compose

DeployLane looks at the root of your repository:

  • With a docker-compose.yml or compose.yml, the whole stack is deployed with Docker Compose. Give the service that should receive web traffic the label deploylane.role: web; several web services are fine.
  • Otherwise DeployLane builds your Dockerfile and runs a single container.

If neither file exists, the app can't be created. The .yaml spellings aren't recognised. Traefik sends traffic to the port your image exposes, so make sure your Dockerfile has an EXPOSE line. A typical compose file:

services:
  web:
    build: .
    labels:
      deploylane.role: web
    environment:
      DATABASE_URL: postgres://app:${POSTGRES_PASSWORD}@db:5432/app
  db:
    image: postgres:17
    environment:
      POSTGRES_USER: app
      POSTGRES_DB: app
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - db-data:/var/lib/postgresql/data

volumes:
  db-data:

Other services, such as the database, are only reachable from inside the app. ${POSTGRES_PASSWORD} is filled in from the app's environment variables (see Environment variables).

App tabs

  • Settings: repository, branch, app name, Dockerfile path and a post-deploy command. That command runs on the server after each deploy, for up to 5 minutes; to run something inside your app, use docker exec in it. A failing command doesn't fail the deploy unless you switch that on.
  • View logs shows the latest lines of your containers' output.
  • Database (apps with Postgres): run a query, or run a .sql file of up to 64 MB in the background. Queries run as the database superuser, so they can change data. They time out after 15 seconds and return at most 1,000 rows.
  • Diagnose checks the DNS of your domains and runs a series of checks on the server.
  • Danger zone: Clone copies the app (code, environment variables, cron jobs and Postgres data) to another server. Reset database empties the app's Postgres databases, after trying to take a snapshot; deploy afterwards so your migrations rebuild the schema. Delete app removes the app from its server.

Deleting a compose app also deletes its Docker volumes, including database data. Backups in Object Storage are kept, so make sure you have a recent one.

Deploys and rollbacks

Deploy builds your app on its own server and replaces the running version:

  1. The repository is cloned with your GitHub token: the app's own token if it has one, otherwise your account's.
  2. If the app has a Postgres database and the server has backup storage, DeployLane takes a database snapshot first. If that snapshot fails, the deploy stops.
  3. Dockerfile apps are built into an image tagged with the commit; compose apps are built and started with Docker Compose.
  4. The deploy succeeds when the new container is still running a few seconds after it starts. This isn't an HTTP health check, so keep an eye on your logs.

One deploy runs per server at a time; others wait in a queue. Deploying the same app again cancels its running deploy. Time limits: 2 minutes to clone, 10 minutes for a Dockerfile build, 15 minutes for a compose build.

Auto-deploy on push

In the app's Settings, under GitHub webhook (auto-deploy), switch on Auto-deploy. Copy the secret (it's shown only once) and the Payload URL. Then, in your GitHub repository, go to Settings → Webhooks → Add webhook and fill in:

  • Payload URL: the URL from DeployLane;
  • Content type: application/json (other types are rejected);
  • Secret: the secret from DeployLane;
  • Events: just the push event.

Every push to the app's branch now starts a deploy.

Rollback, restart, stop

  • Deployments lists your deploys, newest first, with the full log of each. Rollback on a successful deploy brings that version back: Dockerfile apps restart the image built for that commit, compose apps are redeployed from it.
  • Restart restarts the containers without rebuilding. It doesn't pick up changed environment variables; deploy for that.
  • Stop and Start do what they say. The nightly cleanup removes containers that have been stopped for over 24 hours; after that, deploy again.

Environment variables

App → Environment → Add variable: a key, a scope and a value. Every value is stored encrypted. Mark as secret also keeps the value out of the dashboard after saving.

  • Scope: all services (shared), or a single compose service. Shared variables can also be used as ${VAR} in your compose file.
  • Changes take effect on the next deploy.
  • Build-time variables: for Dockerfile apps, variables starting with NEXT_PUBLIC_, VITE_, PUBLIC_, REACT_APP_, EXPO_PUBLIC_ or NUXT_PUBLIC_ are also passed as build arguments; declare them with ARG in your Dockerfile. For compose apps, list them under build.args.

Domains and HTTPS

Every web service gets an automatic domain under apps.deploylane.dev with HTTPS, so you can try your app before pointing your own domain at it.

To add your own domain, open App → Domains & SSL → Add domain, enter the hostname and pick a role:

  • Make primary: the main address of the app;
  • Alias: serves the same content under another name;
  • Redirect → primary (301): sends visitors on to the primary domain.

Then point the hostname at your server at your DNS provider: an A record to the server's IPv4 address (optionally AAAA for IPv6), or for a subdomain a CNAME to the app's automatic domain. The Domains tab shows the values and checks DNS and certificates for you. Once DNS resolves, Traefik gets a Let's Encrypt certificate, usually within a minute.

Domain changes apply without a redeploy. Compose apps with several web services get a route per service (Add another route). There's no automatic www redirect: add www as a redirect domain if you want one.

Backups and restores

DeployLane backs up Postgres databases with pg_dump to Hetzner Object Storage in your own project. This needs the Object Storage key pair from Getting started.

  1. Server → Backups → Configure backups.
  2. Set up backup storage: pick a region (Falkenstein, Nuremberg or Helsinki) and a bucket name, then Create bucket. Each server has one bucket.
  3. Add backup job: the app and its Postgres service, a job name, a schedule (cron) and optionally a completion webhook. The default is daily at 03:00 UTC; any cron schedule works, hourly included.

DeployLane recognises a Postgres service by its postgres or postgis image, or by the label deploylane.role: database. The container needs POSTGRES_USER and POSTGRES_DB. Besides the schedule, DeployLane takes a snapshot before every deploy, before every restore and before enabling point-in-time recovery. Edit backup retention sets how long snapshots are kept: 7 days by default, anywhere from 1 to 30 days.

Restoring a snapshot

Click Restore next to a snapshot, confirm, type the app name and click Restore now. DeployLane first takes a safety snapshot of the current database, then stops the app's web and worker services, recreates the database from the snapshot and starts everything again. A restore goes into the same database and can take up to 30 minutes.

Point-in-time recovery

In Edit backup job, choose Enable & redeploy. DeployLane then continuously archives your database's changes with pgBackRest and takes a full backup every night, keeping the last two. You can restore to any moment since the oldest full backup it keeps, typically the last 24 to 48 hours: choose Restore to point in time, pick the moment, type the app name and click Restore now.

When a backup fails, or works again after failing, DeployLane emails you.

Cron jobs

App → Cron → Add cron job: a name, a schedule (cron), the HTTP method and URL, a timeout (10 to 3,600 seconds, default 60), and optionally request headers and a completion webhook.

  • The URL is called from your server, so internal addresses work too, such as http://myapp-app:3000/cron for a Dockerfile app named myapp.
  • Write ${VAR} in a header value to insert one of the app's environment variables, which keeps secrets out of the job itself.
  • Any 2xx response counts as success. Failed runs are not retried.
  • Run now runs a job straight away; Run history shows the last 20 runs. Schedules use the server's clock, which is UTC on servers DeployLane creates.

The Cron jobs page in the sidebar lists the jobs of all your apps. When a job starts failing, or works again, DeployLane emails you.

Blob storage

App → Blob storage → Provision blob storage creates a public bucket, a private bucket or both in your Hetzner project, for servers in Falkenstein, Nuremberg or Helsinki. DeployLane adds these environment variables to the app; deploy again to use them with any S3-compatible client:

BLOB_ENDPOINT, BLOB_ACCESS_KEY, BLOB_SECRET_KEY, BLOB_PUBLIC_BUCKET, BLOB_PUBLIC_URL, BLOB_PRIVATE_BUCKET, BLOB_PRIVATE_URL.

The file browser lets you upload (up to 100 files at a time), download and delete files. The access keys are your project's Object Storage key pair, which can reach every bucket in that Hetzner project.

Umami analytics

Server → Settings → Analytics → Enable Umami installs Umami, with its own Postgres database, on an automatic HTTPS domain. The card shows the admin login and lets you update, turn off or open Umami. Its database is backed up daily at 03:30 UTC; if the server has no backup storage yet, DeployLane creates a bucket that keeps 7 days. You can run one Umami per server, and it needs the Object Storage key pair.

Through the MCP server, your AI assistant can add Umami tracking to your apps and answer questions about your traffic.

AI assistants (MCP)

DeployLane's MCP server lets AI assistants such as Claude Code and Cursor inspect and manage your apps.

  1. Settings → API access → MCP tokens → Generate token. Give it a name and a scope: Read-only (recommended) or Full access. The token starts with mcp_ and is shown once.
  2. Add the server to your AI client.

Claude Code:

claude mcp add deploylane https://mcp.deploylane.dev \
  --transport http \
  --header "Authorization: Bearer mcp_..."

Cursor (~/.cursor/mcp.json) and other clients with a JSON config:

{
  "mcpServers": {
    "deploylane": {
      "url": "https://mcp.deploylane.dev",
      "headers": {
        "Authorization": "Bearer mcp_..."
      }
    }
  }
}

Any client that speaks MCP over HTTP works: point it at https://mcp.deploylane.dev with the header Authorization: Bearer <token>.

  • Read-only tokens can list and inspect servers, apps, deploys, logs, metrics, cron jobs, backups and Umami statistics.
  • Full access adds actions: creating apps, deploying, rolling back, starting and stopping apps, setting environment variables, managing domains and cron jobs, and rebooting servers.
  • Limits per account: 120 light calls and 20 heavy calls a minute (heavy means anything that writes or connects to a server), and one deploy per app per minute.
  • View activity shows a token's last 30 calls. Revoke deletes the token and its activity.

REST API

Apps can manage their own domain aliases through a small REST API, which is useful when your customers bring their own domain. Create a token under Settings → API access → Domain alias tokens → Generate token. A token starts with dla_, works for one app and is shown once.

MethodPathWhat it does
GET/api/v1/apps/:appId/domain-aliasesList the app's routes and aliases
POST/api/v1/apps/:appId/domain-aliasesAdd an alias: {"hostname": "…"}
DELETE/api/v1/apps/:appId/domain-aliases/:hostnameRemove an alias
curl -X POST https://app.deploylane.dev/api/v1/apps/42/domain-aliases \
  -H "Authorization: Bearer dla_..." \
  -H "Content-Type: application/json" \
  -d '{"hostname":"shop.customer.example"}'

The base URL is https://app.deploylane.dev, with the token in an Authorization: Bearer header. For apps with several web services, add service_name (in the body for POST, as a query parameter otherwise). The POST response includes the DNS records your customer needs to create; once DNS resolves, the certificate follows automatically. Each token can make about 60 requests a minute. DeployLane doesn't check who owns a hostname, so verify that on your side.

Getting help

Stuck, or found a bug? Email support@deploylane.dev or use the contact form; we reply within one business day. For outages, check the status page.