Â
Â
FTL is a deployment tool that reduces complexity for projects that don't require extensive orchestration infrastructure. It provides automated deployment to cloud providers like Hetzner, DigitalOcean, Linode, and custom servers without the overhead of CI/CD pipelines or container orchestration platforms.
- Single YAML configuration file with environment variable substitution
- Zero-downtime deployments
- Automatic SSL/TLS certificate management
- Docker-based deployment with built-in health checks
- Integrated Nginx reverse proxy
- Multi-provider support (Hetzner, DigitalOcean, Linode, custom servers)
- Fetch and stream logs from deployed services
- Establish SSH tunnels to remote dependencies
-
Via Homebrew (macOS and Linux)
brew tap yarlson/ftl brew install ftl
-
Download from GitHub releases
curl -L https://github.com/yarlson/ftl/releases/latest/download/ftl_$(uname -s)_$(uname -m).tar.gz | tar xz sudo mv ftl /usr/local/bin/
-
Build from source
go install github.com/yarlson/ftl@latest
Create an ftl.yaml
configuration file in your project directory:
project:
name: my-project
domain: my-project.example.com
email: [email protected]
servers:
- host: my-project.example.com
port: 22
user: my-project
ssh_key: ~/.ssh/id_rsa
services:
- name: my-app
image: my-app:latest
port: 80
health_check:
path: /
interval: 10s
timeout: 5s
retries: 3
routes:
- path: /
strip_prefix: false
dependencies:
- name: postgres
image: postgres:16
volumes:
- postgres_data:/var/lib/postgresql/data
env:
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_USER=${POSTGRES_USER:-postgres}
- POSTGRES_DB=${POSTGRES_DB:-app}
volumes:
- postgres_data
Environment variables in the configuration can be:
- Required:
${VAR_NAME}
- Must be set in the environment - Optional with default:
${VAR_NAME:-default_value}
- Uses default if not set
Set up your server with the required dependencies:
ftl setup
This command will:
- Install Docker and other necessary packages on your server
- Configure firewall rules
- Set up user permissions
- Initialize Docker networks
Deploy your application to the configured servers:
ftl deploy
This command will:
- Connect to your servers via SSH
- Pull Docker images specified in your configuration
- Start new containers with health checks
- Configure the Nginx reverse proxy
- Manage SSL/TLS certificates via ACME
- Perform zero-downtime container replacement
- Clean up unused resources
Retrieve logs from your deployed services:
ftl logs [service] [flags]
- service: (Optional) Name of the service to fetch logs from. If omitted, logs from all services are fetched.
- flags:
-f
,--follow
: Stream logs in real-time.-n
,--tail
: Number of lines to show from the end of the logs.
-
Fetch logs from all services:
ftl logs
-
Stream logs from a specific service:
ftl logs my-app -f
-
Fetch the last 50 lines of logs from all services:
ftl logs -n 50
Establish SSH tunnels for your dependencies, allowing local access to services running on your server:
ftl tunnels [flags]
This command will:
- Connect to your server via SSH
- Forward local ports to remote ports for all dependencies defined in your configuration
- Allow you to interact with your dependencies locally as if they were running on your machine
-s
,--server
: (Optional) Specify the server name or index to connect to, if multiple servers are defined.
-
Establish tunnels to all dependency ports:
ftl tunnels
-
Specify a server to connect to (if multiple servers are configured):
ftl tunnels --server my-project.example.com
Press Ctrl+C
to terminate the tunnels when you're done.
The ftl tunnels
command is useful for:
- Accessing dependency services (e.g., databases) running on your server from your local machine
- Simplifying local development by connecting to remote services without modifying your code
- Testing and debugging your application against live dependencies
FTL manages deployments and log retrieval through these main components:
- Installs required packages (Docker, basic tools)
- Configures firewall rules
- Sets up user permissions
- Initializes Docker networks
- Connects to configured servers via SSH
- Pulls specified Docker images
- Starts new containers with health checks
- Configures Nginx reverse proxy
- Manages SSL/TLS certificates via ACME
- Performs zero-downtime container replacement
- Cleans up unused resources
- Fetches logs from specified services
- Supports real-time streaming with the
-f
flag - Allows limiting the number of log lines with the
-n
flag
- Connects to your server via SSH
- Establishes port forwarding from local ports to remote ports for all defined dependencies
- Maintains active tunnels with keep-alive packets
- Allows for graceful shutdown upon user interruption (Ctrl+C)
- Web applications with straightforward deployment needs
- Projects requiring automated SSL and reverse proxy setup
- Small to medium services running on single or multiple servers
- Teams seeking to minimize deployment infrastructure
- Applications requiring environment-specific configurations
- Complex microservice architectures requiring service mesh
- Systems needing advanced orchestration features
- Multi-region deployment coordination
- Specialized compliance environments
project:
name: string # Project identifier (required)
domain: string # Primary domain (required, must be FQDN)
email: string # Contact email (required, valid email format)
servers:
- host: string # Server hostname/IP (required, FQDN or IP)
port: int # SSH port (required, 1-65535)
user: string # SSH user (required)
ssh_key: string # Path to SSH key file (required)
services:
- name: string # Service identifier (required)
image: string # Docker image (required)
port: int # Container port (required, 1-65535)
path: string # Service path (default: "./")
command: string # Override container command
entrypoint: [string] # Override container entrypoint
health_check:
path: string # Health check endpoint
interval: duration # Time between checks
timeout: duration # Check timeout
retries: int # Number of retries
routes:
- path: string # Route path prefix (required)
strip_prefix: bool # Strip prefix from requests
volumes: [string] # Volume mappings (format: "volume:path")
env: # Environment variables
- KEY=value
dependencies:
- name: string # Dependency name (required)
image: string # Docker image (required)
volumes: [string] # Volume mappings (format: "volume:path")
env: # Environment variables
- KEY=value
ports: [int] # Ports to expose for SSH tunneling
volumes: [string] # Named volumes list
FTL supports two forms of environment variable substitution in the configuration:
-
Required Variables:
${VAR_NAME}
- Must be present in the environment
- Deployment fails if variable is not set
-
Variables with Defaults:
${VAR_NAME:-default_value}
- Uses the environment variable if set
- Falls back to the default value if not set
- Health Checks: Customize health check endpoints, intervals, timeouts, and retries for each service.
- Volume Management: Define named volumes for persistent data storage.
- Environment Variables: Set environment variables for services and dependencies, with support for environment variable substitution.
- Service Dependencies: Specify dependent services and their configurations.
- Routing Rules: Define custom routing paths and whether to strip prefixes.
- SSH Tunnels: Specify ports in dependencies to enable SSH tunneling for local access.
The ftl-examples repository contains reference implementations:
- Flask - Python Flask application with PostgreSQL
- More examples coming soon
Each example provides a complete project structure with configuration files and deployment instructions.
# Clone repository
git clone https://github.com/yarlson/ftl.git
# Install dependencies
cd ftl
go mod download
# Run tests
go test ./...
Contributions are welcome. Please ensure:
- Code follows project style guidelines
- Tests pass and new tests are added for new features
- Documentation is updated accordingly