1
0
Fork 0

Documentation Refactor

This commit is contained in:
Joao Gilberto Magalhaes 2026-02-20 01:05:37 -05:00
parent 3b8818e636
commit a410b34521
47 changed files with 2065 additions and 3911 deletions

View file

@ -0,0 +1,4 @@
{
"label": "Guides",
"position": 2
}

268
docs/guides/acme.md Normal file
View file

@ -0,0 +1,268 @@
---
sidebar_position: 2
sidebar_label: "ACME / Let's Encrypt"
---
# SSL - Automatic Certificate Management Environment (ACME)
The Automatic Certificate Management Environment (ACME) protocol
allow automating interactions between certificate authorities and their users' servers,
allowing the automated deployment of public key infrastructure.
Most of the issuers offers Automatic Issuing free of cost.
## Supported ACME Challenge Methods
Easy HAProxy supports the following ACME challenge types:
- **HTTP-01 Challenge (Default and Only)**
The ACME server validates ownership by making an HTTP request to a temporary endpoint served on port 80. Easy HAProxy provisions a standalone Certbot responder on an internal port and routes `/.well-known/acme-challenge/` traffic to it.
:::info Challenge Support
- **HTTP-01**: Fully supported (default)
- **TLS-ALPN-01**: Not supported natively by Easy HAProxy
- **DNS-01**: Not supported natively. If you need DNS-01 for wildcard certificates, obtain certificates externally and mount them via `sslcert` as static certificates.
:::
## How ACME works with Easy HAProxy
At a high level, ACME with Easy HAProxy works in two stages:
1. Global ACME/Certbot setup (one-time per EasyHAProxy instance)
- Choose your Certificate Authority (CA) either by:
- Using AUTOCONFIG with `EASYHAPROXY_CERTBOT_AUTOCONFIG` (e.g., zerossl, letsencrypt_test, google, etc.), or
- Manually setting `EASYHAPROXY_CERTBOT_SERVER` (and `EASYHAPROXY_CERTBOT_EAB_KID` / `EASYHAPROXY_CERTBOT_EAB_HMAC_KEY` when your CA requires EAB).
- Always set your contact email via `EASYHAPROXY_CERTBOT_EMAIL`.
- Ensure ports 80 and 443 are publicly reachable on the EasyHAProxy host.
- Persist the folder `/etc/easyhaproxy/certs/certbot` on a durable volume so issued/renewed certificates survive container restarts and avoid hitting CA rate limits.
- Challenge method is HTTP-01 only; EasyHAProxy configures a standalone Certbot responder internally.
2. Enable ACME per domain (per service/app)
- Add the label `easyhaproxy.<definition>.certbot=true` to the service you want a certificate for.
- Ensure the service is exposed on HTTP port 80 from EasyHAProxy's perspective (e.g., `easyhaproxy.<definition>.port=80`). ACME HTTP-01 will not work if the front port is not 80.
- Provide the domain via `easyhaproxy.<definition>.host=yourdomain.tld` (and additional labels per your install method).
What happens under the hood
- When a labeled domain is detected and a certificate is needed, EasyHAProxy runs Certbot with `--preferred-challenges http` and a standalone responder bound to internal port 2080.
- HAProxy temporarily routes `/.well-known/acme-challenge/` for that domain to the Certbot responder, allowing the CA to validate via HTTP-01.
- On success, EasyHAProxy merges the issued cert and key and stores them under `/etc/easyhaproxy/certs/certbot` (one PEM per domain), then reloads HAProxy to serve HTTPS for that domain.
- Certificates are monitored and renewed automatically before expiry.
Tips
- Do not map port 443 for your backend app; EasyHAProxy will terminate TLS at the proxy once the certificate is issued.
- If you do not set `EASYHAPROXY_CERTBOT_EMAIL`, EasyHAProxy will not request certificates.
- DNS-01 is not supported natively; for wildcards or DNS-only environments, issue certificates externally and mount them via `sslcert` as static certificates.
## Environment Variables
To enable the ACME protocol we need to enable Certbot in EasyHAProxy by setting up the following environment variables:
| Environment Variable | Required? | Description |
|------------------------------------------|-----------|----------------------------------------------------------------------------------------------------------------------------------|
| EASYHAPROXY_CERTBOT_EMAIL | **YES** | Your email for the certificate authority. Required for certificate issuance. |
| EASYHAPROXY_CERTBOT_AUTOCONFIG | **YES\*** | Pre-configured settings for your Certificate Authority (CA). See table below. **Required if CERTBOT_SERVER is not set.** |
| EASYHAPROXY_CERTBOT_SERVER | **YES\*** | The ACME endpoint URL of your certificate authority. **Required if AUTOCONFIG is not set.** Auto-set when using AUTOCONFIG. |
| EASYHAPROXY_CERTBOT_EAB_KID | - | External Account Binding (EAB) Key Identifier (KID) provided by your certificate authority. Some CA require it. See table below. |
| EASYHAPROXY_CERTBOT_EAB_HMAC_KEY | - | External Account Binding (EAB) HMAC Key provided by your certificate authority. Some CA require it. See table below. |
| EASYHAPROXY_CERTBOT_RETRY_COUNT | - | Wait 'n' requests before retrying issue invalid requests. Default 60. |
| EASYHAPROXY_CERTBOT_PREFERRED_CHALLENGES | - | The preferred challenges for Certbot. Available: `http` |
| EASYHAPROXY_CERTBOT_MANUAL_AUTH_HOOK | - | The path to a script that will be executed (default: None) |
**\*Important:** You must set **either** `EASYHAPROXY_CERTBOT_AUTOCONFIG` **or** `EASYHAPROXY_CERTBOT_SERVER` (not both). Using `AUTOCONFIG` is recommended as it automatically configures the server URL for popular certificate authorities.
## Auto Config Certificate Authority (CA)
Here are detailed instructions per Certificate Authority (CA). If anyone is missing, please let's know.
Possible values for: `EASYHAPROXY_CERTBOT_AUTOCONFIG`
| CA | Auto Config | Free? | Account Required? | EAB KID? | EAB HMAC Key? | More Info |
|----------------------|------------------|-------|--------------------|----------|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Let's Encrypt | letsencrypt | Yes | No | No | No | Default when no AUTOCONFIG is set |
| Let's Encrypt (Test) | letsencrypt_test | Yes | No | No | No | - |
| ZeroSSL | zerossl | Yes | No | No | No | [Link](https://zerossl.com/documentation/acme/) |
| BuyPass | buypass | Yes | No | No | No | [Link](https://community.buypass.com/t/63d4ay/buypass-go-ssl-endpoints-updated-14-05-2020) |
| BuyPass (test) | buypass_test | Yes | No | No | No | [Link](https://community.buypass.com/t/63d4ay/buypass-go-ssl-endpoints-updated-14-05-2020) |
| Google | google | Yes | Yes | Yes | Yes | [Link](https://cloud.google.com/blog/products/identity-security/automate-public-certificate-lifecycle-management-via--acme-client-api) |
| Google Test | google_test | Yes | Yes | Yes | Yes | [Link](https://cloud.google.com/blog/products/identity-security/automate-public-certificate-lifecycle-management-via--acme-client-api) |
| SSLCOM RCA | sslcom_rca | Trial | EAB Keys by email. | Yes | Yes | [Link](https://www.ssl.com/blogs/sslcom-supports-acme-protocol-ssl-tls-certificate-automation/) |
| SSLCOM ECC | sslcom_ecc | Trial | EAB Keys by email. | Yes | Yes | [Link](https://www.ssl.com/blogs/sslcom-supports-acme-protocol-ssl-tls-certificate-automation/) |
| Digicert | - | No | Yes | Yes | Yes | [Link](https://docs.digicert.com/en/certcentral/certificate-tools/certificate-lifecycle-automation-guides/use-a-third-party-acme-client-for-host-automations.html) |
| Entrust | - | No | Yes | Yes | Yes | [Link](https://www.entrust.com/knowledgebase/ssl/how-to-use-acme-to-install-ssl-tls-certificates-in-entrust-certificate-services-apache) |
| Sectigo | - | No | Yes | Yes | Yes | [Link](https://www.sectigo.com/resource-library/sectigos-acme-automation) |
This configuration is global. After set up ACME properly, is necessary enable for each domain the certificate request.
To do that add the label: `easyhaproxy.<definition>.certbot=true`. See the method of installation you are using to learn how to set up properly.
## Example
### Setting up EasyHAProxy
Run the EasyHAProxy container:
```bash
docker run \
... \
-e EASYHAPROXY_CERTBOT_AUTOCONFIG=zerossl \
-e EASYHAPROXY_CERTBOT_EMAIL=john@doe.com \
-p 80:80 \
-p 443:443 \
-v /path/to/guest/certbot/certs:/etc/easyhaproxy/certs/certbot \
... \
byjg/easy-haproxy
```
:::note Configuration Notes
- The `EASYHAPROXY_CERTBOT_AUTOCONFIG` is not required for Let's Encrypt (it's the default). In this example, the certificate will be issued by ZeroSSL.
- If you don't set the `EASYHAPROXY_CERTBOT_EMAIL` environment variable, EasyHAProxy will fail silently and **will not request** certificates.
- Ports 80 and 443 must be accessible through the internet as a [Let's Encrypt requirement](https://letsencrypt.org/docs/allow-port-80/)
:::
:::danger Important: Persist Certbot Certificates
To avoid hitting rate limits and certificate issuing problems:
- **You must persist** the container folder `/etc/easyhaproxy/certs/certbot` outside the container
- **Never delete or modify** its contents manually
- If you don't persist this folder, or if you delete/modify its contents, certificate issuing may not work properly and you may hit rate limits
:::
If you are using Let's Encrypt, be aware of it rate limits:
- https://letsencrypt.org/docs/duplicate-certificate-limit/
- https://letsencrypt.org/docs/rate-limits/
## Setting up your container to use the ACME CA
```bash
docker run \
... \
--label easyhaproxy.express.port=80 \
--label easyhaproxy.express.localport=3000 \
--label easyhaproxy.express.host=example.org \
--label easyhaproxy.express.certbot=true \
... \
some/myimage
```
:::warning ACME Requirements
- Your container **must** be configured to listen on port 80 (`easyhaproxy.<definition>.port=80`). The CA will not issue certificates if using another port, and EasyHAProxy will fail silently.
- Do not set port 443 for the container when using ACME, because EasyHAProxy will create the HTTPS binding automatically once the certificate is issued.
:::
## Complete Docker Compose Example
Here's a complete `docker-compose.yml` showing proper ACME configuration:
```yaml
services:
easyhaproxy:
image: byjg/easy-haproxy:6.0.0
volumes:
- /var/run/docker.sock:/var/run/docker.sock
# REQUIRED: Persist Certbot certificates (ACME)
- certs_certbot:/etc/easyhaproxy/certs/certbot
# OPTIONAL: For manual certificates (see SSL documentation)
- certs_haproxy:/etc/easyhaproxy/certs/haproxy
environment:
# Service discovery
EASYHAPROXY_DISCOVER: docker
EASYHAPROXY_LABEL_PREFIX: easyhaproxy
# ACME/Certbot Configuration (Method 1: Recommended)
EASYHAPROXY_CERTBOT_EMAIL: your-email@example.com
EASYHAPROXY_CERTBOT_AUTOCONFIG: letsencrypt
# Other settings
EASYHAPROXY_SSL_MODE: "default"
HAPROXY_CUSTOMERRORS: "true"
HAPROXY_USERNAME: admin
HAPROXY_PASSWORD: password
HAPROXY_STATS_PORT: 1936
ports:
- "80:80/tcp"
- "443:443/tcp"
- "1936:1936/tcp"
healthcheck:
test: ["CMD", "curl", "-f", "-u", "admin:password", "http://localhost:1936"]
interval: 10s
timeout: 5s
start_period: 30s
retries: 3
# Example backend service with ACME enabled
myapp:
image: nginx:alpine
labels:
easyhaproxy.http.host: example.com
easyhaproxy.http.port: 80
easyhaproxy.http.localport: 80
easyhaproxy.http.certbot: "true" # Enable ACME for this domain
volumes:
certs_certbot:
# This volume MUST be persisted to avoid rate limits
certs_haproxy:
# Optional: only needed if using manual certificates
```
## Certificate Storage Paths
EasyHAProxy uses different paths for different certificate types:
| Path | Purpose | When to Mount |
|----------------------------------|-------------------------------------|----------------------------------------------|
| `/etc/easyhaproxy/certs/certbot` | ACME/Certbot automatic certificates | **Required** when using ACME |
| `/etc/easyhaproxy/certs/haproxy` | Manual/custom certificates | Optional - only if using custom certificates |
Both volumes can be mounted simultaneously. Per-domain certificate selection:
- If a domain has `certbot=true` label, ACME certificate is used
- Otherwise, manual certificate from `/etc/easyhaproxy/certs/haproxy` is used (if present)
## Troubleshooting
### Warning: "ACME environment not ready: ACME server not configured"
**Cause:** You set `EASYHAPROXY_CERTBOT_EMAIL` but forgot to configure the ACME server.
**Solution:** Add one of these to your environment variables:
```yaml
# Option 1: Use AUTOCONFIG (recommended)
EASYHAPROXY_CERTBOT_AUTOCONFIG: letsencrypt
# Option 2: Set server manually
EASYHAPROXY_CERTBOT_SERVER: https://acme-v02.api.letsencrypt.org/directory
```
### Certificates Not Being Issued
**Common causes:**
1. Port 80 is not publicly accessible
2. DNS doesn't point to your server
3. Container label missing `certbot=true`
4. Container port is not 80 (`easyhaproxy.<definition>.port` must be 80)
5. Rate limits hit (check `/etc/easyhaproxy/certs/certbot` volume)
**Debug steps:**
```bash
# Check EasyHAProxy logs
docker logs easyhaproxy
# Check if Certbot volume is persisted
docker volume inspect certs_certbot
# Verify port 80 is accessible
curl -I http://your-domain.com/.well-known/acme-challenge/test
```
### Rate Limit Errors
If you hit Let's Encrypt rate limits:
- Wait for the limit window to reset (usually 1 week)
- Use staging server for testing: `EASYHAPROXY_CERTBOT_AUTOCONFIG: letsencrypt_test`
- Ensure `/etc/easyhaproxy/certs/certbot` volume is properly persisted
- See: https://letsencrypt.org/docs/rate-limits/
----
[Open source ByJG](http://opensource.byjg.com)

View file

@ -0,0 +1,15 @@
---
sidebar_position: 9
sidebar_label: "DigitalOcean"
---
# DigitalOcean
DigitalOcean is a cloud infrastructure provider focused on simplifying web infrastructure for software developers.
EasyHAProxy is available on DigitalOcean Marketplace.
You can install it with a few clicks directly from the DigitalOcean dashboard.
## Installing EasyHAProxy on DigitalOcean
Please refer the [EasyHAProxy page on DigitalOcean Marketplace](https://marketplace.digitalocean.com/apps/easyhaproxy-ingress-controller).

12
docs/guides/dokku.md Normal file
View file

@ -0,0 +1,12 @@
---
sidebar_position: 8
sidebar_label: "Dokku"
---
# Dokku
Dokku is a Docker powered mini-Heroku in around 100 lines of Bash. It is the smallest PaaS implementation you've ever seen, weighing in at a little over 100 MB of memory usage and about 80 MB of disk space.
## Installing EasyHAProxy on Dokku
Please refer to the [official documentation](https://github.com/dokku/dokku/blob/master/docs/networking/proxies/haproxy.md).

50
docs/guides/helm.md Normal file
View file

@ -0,0 +1,50 @@
---
sidebar_position: 6
sidebar_label: "Helm"
---
# Helm 3
Helm is a package manager for Kubernetes. It allows you to install and manage applications on Kubernetes.
## Setup EasyHAProxy with Helm 3
### 1) Install EasyHAProxy
```bash title="Add the Helm repository"
helm repo add byjg https://opensource.byjg.com/helm
helm repo update byjg
kubectl create namespace easyhaproxy
```
```bash title="Install with Helm"
helm upgrade --install ingress byjg/easyhaproxy \
--namespace easyhaproxy \
--set resources.requests.cpu=100m \
--set resources.requests.memory=128Mi
```
### 2) Choose a deployment mode
By default, EasyHAProxy installs as a **DaemonSet** (`service.create: false`). To use the recommended **NodePort** or **ClusterIP** modes instead, set `service.create: true`:
```bash title="NodePort (recommended)"
helm upgrade --install ingress byjg/easyhaproxy \
--namespace easyhaproxy \
--set service.create=true \
--set service.type=NodePort
```
```bash title="ClusterIP (behind LoadBalancer)"
helm upgrade --install ingress byjg/easyhaproxy \
--namespace easyhaproxy \
--set service.create=true \
--set service.type=ClusterIP
```
See [Deployment Modes](../getting-started/kubernetes.md#deployment-modes) for a comparison of all three modes.
For the complete list of configurable values, see the [Helm Values reference](../reference/helm.md).
----
[Open source ByJG](http://opensource.byjg.com)

81
docs/guides/microk8s.md Normal file
View file

@ -0,0 +1,81 @@
---
sidebar_position: 7
sidebar_label: "MicroK8s"
---
# Microk8s Add-ons
Microk8s is a lightweight Kubernetes distribution that can run on a single machine. It is very easy to install and use.
You can add custom addons to your Microk8s installation.
Here are the steps to install EasyHAProxy on your Microk8s.
## Enabling EasyHAProxy on MicroK8s
EasyHAProxy is being part of official MicroK8s Community edition since MicroK8s version 1.27.
Just enable the community add-on:
```bash title="Enable community addons"
microk8s enable community
```
and you'll see:
```
$ microk8s status
microk8s is running
...
addons:
...
disabled:
easyhaproxy # (community) EasyHAProxy can detect and configure HAProxy automatically based on ingress labels
```
However, if you are using MicroK8s before 1.27 you need to enable it directly from the ByJG repository by accessing the microk8s host machine and run:
```bash title="Add ByJG addon repository"
microk8s addons repo add byjg https://github.com/byjg/microk8s-addons.git
```
And you should see:
```text
$ microk8s status
microk8s is running
...
addons:
...
disabled:
easyhaproxy # (byjg) EasyHAProxy can detect and configure HAProxy automatically based on ingress labels
....
```
## Installing EasyHAProxy addon
Once you have enable the EasyHAProxy from the community repository or from ByJG repository and can enable it by running:
Usage:
Install as a Daemonset:
```bash title="Install as DaemonSet"
microk8s enable easyhaproxy
```
Install as a NodePort:
```bash title="Install as NodePort"
microk8s enable easyhaproxy --nodeport
```
:::warning Disable Other Ingress Controllers
You need to disable any ingress controller you have previously installed (e.g., nginx, traefik, etc.) before installing EasyHAProxy to avoid conflicts.
:::
For more parameters you can refer to the [Kubernetes](../getting-started/kubernetes.md) page.
----
[Open source ByJG](http://opensource.byjg.com)

View file

@ -0,0 +1,695 @@
---
sidebar_position: 4
sidebar_label: "Plugin Development"
---
# Plugin Development Guide
This comprehensive guide covers everything you need to know about developing plugins for EasyHAProxy. Plugins extend HAProxy configuration with custom functionality and can be integrated seamlessly with Docker, Kubernetes, and Swarm environments.
## Table of Contents
1. [Overview](#overview)
2. [Plugin Architecture](#plugin-architecture)
3. [Quick Start Guide](#quick-start-guide)
4. [API Reference](#api-reference)
5. [Advanced Examples](#advanced-examples)
6. [Best Practices](#best-practices)
7. [Testing Guidelines](#testing-guidelines)
8. [Troubleshooting](#troubleshooting)
9. [Distribution](#distribution)
---
## Overview
### What is a Plugin?
A plugin is a Python class that implements the `PluginInterface` and extends HAProxy's configuration during the discovery cycle. Plugins can:
- **Inject HAProxy configuration** - Add custom HAProxy directives (ACLs, http-request rules, etc.)
- **Modify discovery data** - Transform the easymapping structure before HAProxy config generation
- **Perform maintenance tasks** - Execute cleanup, monitoring, or integration tasks
- **Integrate with external services** - Connect to APIs, databases, or third-party systems
### Why Build a Plugin?
Build a plugin when you need to:
- Add domain-specific HAProxy configuration based on labels/annotations
- Integrate with CDNs, load balancers, or security services
- Implement custom authentication or authorization logic
- Perform scheduled maintenance or monitoring tasks
- Extend EasyHAProxy without modifying core code
### Plugin System Benefits
- **Zero code changes** - Plugins don't modify EasyHAProxy core
- **Hot reload support** - Plugins reload on each discovery cycle
- **Configuration flexibility** - Configure via YAML, environment variables, or container labels
- **Error isolation** - Plugin errors don't crash the main application (configurable)
- **Easy distribution** - Share plugins as single Python files
---
## Plugin Architecture
### Plugin Types
EasyHAProxy supports two plugin execution models:
#### 1. GLOBAL Plugins
Execute **once per discovery cycle**, regardless of discovered domains.
**Execution timing:** After discovery, before domain processing
**Use cases:**
- Cleanup tasks (removing old temp files)
- Global monitoring (health checks, metrics)
- DNS updates (updating external DNS records)
- Log rotation or archiving
- Integration with global services
**Example:** CleanupPlugin - removes old temporary files once per cycle
#### 2. DOMAIN Plugins
Execute **once per discovered domain/host**.
**Execution timing:** During domain processing, before backend config generation
**Use cases:**
- Domain-specific HAProxy rules (IP whitelisting, rate limiting)
- CDN integration (Cloudflare IP restoration)
- Path-based controls (blocking specific URLs)
- Custom headers or redirects per domain
- JWT validation or authentication
**Example:** CloudflarePlugin - restores visitor IP for each Cloudflare-enabled domain
### Plugin Lifecycle
```
1. LOAD PHASE
├─ PluginManager scans plugins directory
├─ Imports plugin modules
├─ Instantiates plugin classes
└─ Categorizes by type (GLOBAL/DOMAIN)
2. CONFIGURE PHASE
├─ Loads configuration from YAML/env
├─ Calls plugin.configure(config) for each plugin
└─ Validates configuration (plugin responsibility)
3. INITIALIZE PHASE
├─ Calls plugin.initialize() for each plugin
├─ Plugins request file system resources (directories, files)
├─ PluginManager processes resource requests
└─ Creates directories and files as needed
4. EXECUTION PHASE (per discovery cycle)
├─ GLOBAL PLUGINS
│ └─ Executes all global plugins once
└─ DOMAIN PLUGINS
└─ For each discovered domain:
└─ Executes all domain plugins
5. RESULT PROCESSING
├─ Collects PluginResult from each plugin
├─ Injects haproxy_config into backend sections
├─ Injects global_configs into global section
├─ Injects defaults_configs into defaults section
├─ Applies modified_easymapping if provided
└─ Logs metadata for debugging
```
### Plugin Loading Order
1. **Builtin plugins** - Loaded from `/src/plugins/builtin/`
2. **External plugins** - Loaded from `/etc/easyhaproxy/plugins/`
Plugins are discovered automatically by filename (`*.py` excluding `__*.py`).
### Data Flow
```
Container Labels/Annotations
Discovery (Docker/K8s/Swarm)
parsed_object: {IP: labels}
[GLOBAL PLUGINS] ← PluginContext (parsed_object, easymapping, env)
easymapping: [list of domain configs]
For each domain:
[DOMAIN PLUGINS] ← PluginContext (domain, port, host_config, ...)
PluginResult → haproxy_config snippets
HAProxy Configuration File
HAProxy Reload
```
---
## Quick Start Guide
### Step 1: Create Plugin File
Create a new Python file in `/etc/easyhaproxy/plugins/` (or builtin location for core plugins):
```python
# /etc/easyhaproxy/plugins/my_plugin.py
import os
import sys
# Add parent directory to path for imports
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from plugins import PluginInterface, PluginType, PluginContext, PluginResult
from functions import logger_easyhaproxy
class MyPlugin(PluginInterface):
"""My custom plugin description"""
def __init__(self):
# Initialize default configuration
self.enabled = True
self.my_setting = "default_value"
@property
def name(self) -> str:
"""Return unique plugin name"""
return "my_plugin"
@property
def plugin_type(self) -> PluginType:
"""Return plugin type (GLOBAL or DOMAIN)"""
return PluginType.DOMAIN
def configure(self, config: dict) -> None:
"""
Configure plugin from YAML/env/labels
Args:
config: Dictionary with plugin configuration
"""
if "enabled" in config:
self.enabled = str(config["enabled"]).lower() in ["true", "1", "yes"]
if "my_setting" in config:
self.my_setting = config["my_setting"]
def process(self, context: PluginContext) -> PluginResult:
"""
Process plugin logic and return result
Args:
context: PluginContext with execution data
Returns:
PluginResult with HAProxy config and metadata
"""
if not self.enabled:
return PluginResult()
# Generate HAProxy configuration
haproxy_config = f"""# My Plugin - Custom functionality
http-request set-header X-My-Header {self.my_setting}"""
return PluginResult(
haproxy_config=haproxy_config,
metadata={
"domain": context.domain,
"setting_value": self.my_setting
}
)
```
### Step 2: Enable Plugin
**Via container label (Docker):**
```yaml
services:
myapp:
labels:
easyhaproxy.http.host: example.com
easyhaproxy.http.plugins: my_plugin
easyhaproxy.http.plugin.my_plugin.my_setting: custom_value
```
**Via YAML configuration:**
```yaml
# /etc/easyhaproxy/static/config.yaml
plugins:
enabled: [my_plugin]
config:
my_plugin:
enabled: true
my_setting: custom_value
```
**Via environment variable:**
```bash
EASYHAPROXY_PLUGINS_ENABLED=my_plugin
EASYHAPROXY_PLUGIN_MY_PLUGIN_MY_SETTING=custom_value
```
### Step 3: Test Plugin
Restart EasyHAProxy and check logs:
```bash
docker-compose restart haproxy
docker-compose logs -f haproxy | grep my_plugin
```
Expected output:
```
[INFO] Loaded external plugin: my_plugin (domain)
[DEBUG] Configured plugin: my_plugin with config: {'my_setting': 'custom_value'}
[DEBUG] Executing domain plugin: my_plugin for domain: example.com
```
---
## API Reference
### PluginInterface
Base class all plugins must inherit from.
```python
class PluginInterface(ABC):
"""Base class all plugins must inherit"""
@property
@abstractmethod
def name(self) -> str:
"""Return the unique plugin name"""
pass
@property
@abstractmethod
def plugin_type(self) -> PluginType:
"""Return the plugin type (GLOBAL or DOMAIN)"""
pass
@abstractmethod
def configure(self, config: dict) -> None:
"""
Configure the plugin with settings from YAML/env/labels
Args:
config: Dictionary with plugin-specific configuration
"""
pass
def initialize(self) -> InitializationResult:
"""
Initialize plugin resources (new in v2.0)
Optional method to request file system resources.
Default implementation returns empty result (no-op).
Returns:
InitializationResult with resource requests
"""
return InitializationResult()
@abstractmethod
def process(self, context: PluginContext) -> PluginResult:
"""
Process the plugin logic and return result
Args:
context: PluginContext with all necessary data
Returns:
PluginResult with HAProxy config snippets and/or modified data
"""
pass
```
**Properties:**
- `name` - Unique identifier (used in configuration and logs)
- `plugin_type` - Execution model (`PluginType.GLOBAL` or `PluginType.DOMAIN`)
**Methods:**
- `configure(config)` - Receives plugin configuration during initialization
- `initialize()` - **[New in v2.0]** Request file system resources (optional)
- `process(context)` - Main execution logic, returns `PluginResult`
### PluginType
Enum defining plugin execution types.
```python
class PluginType(Enum):
"""Plugin execution types"""
GLOBAL = "global" # Execute once per discovery cycle
DOMAIN = "domain" # Execute per domain/host
```
### PluginContext
Container for all plugin execution data.
```python
@dataclass
class PluginContext:
"""Container for all plugin execution data"""
parsed_object: dict # {IP: labels} from discovery
easymapping: list # Current HAProxy mapping structure
container_env: dict # Environment configuration
domain: Optional[str] = None # Domain name (for DOMAIN plugins)
port: Optional[str] = None # Port (for DOMAIN plugins)
host_config: Optional[dict] = None # Domain-specific config
```
### PluginResult
Plugin execution result containing configuration and metadata.
```python
@dataclass
class PluginResult:
"""Plugin execution result"""
haproxy_config: str = "" # HAProxy config snippet to inject
modified_easymapping: Optional[list] = None # Modified easymapping structure
metadata: Dict[str, Any] = field(default_factory=dict) # Plugin metadata for logging
global_configs: list[str] = field(default_factory=list) # [New] Global-level configs
defaults_configs: list[str] = field(default_factory=list) # [New] Defaults-level configs
```
**Fields:**
- `haproxy_config` - HAProxy configuration snippet (injected into backend/frontend)
- `modified_easymapping` - Modified easymapping structure (optional, advanced use)
- `metadata` - Dictionary with debugging/logging information
- `global_configs` - **[New in v2.0]** List of global-level HAProxy configs (e.g., fcgi-app definitions)
- `defaults_configs` - **[New in v2.0]** List of defaults-level HAProxy configs (e.g., log-format)
### ResourceRequest
**[New in v2.0]** Request for file system resources during plugin initialization.
```python
@dataclass
class ResourceRequest:
"""Request for file system resources"""
resource_type: str # "directory" or "file"
path: str
content: str | None = None
overwrite: bool = False
```
### InitializationResult
**[New in v2.0]** Plugin initialization result with resource requests.
```python
@dataclass
class InitializationResult:
"""Plugin initialization result with resource requests"""
resources: list[ResourceRequest] = field(default_factory=list)
metadata: dict[str, Any] = field(default_factory=dict)
```
---
## Advanced Examples
### Example 1: IP Whitelist Plugin (DOMAIN)
Restrict access to specific IP addresses per domain.
```python
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from plugins import PluginInterface, PluginType, PluginContext, PluginResult
class IpWhitelistPlugin(PluginInterface):
"""Plugin to restrict access to specific IP addresses"""
def __init__(self):
self.enabled = True
self.allowed_ips = []
self.status_code = 403
@property
def name(self) -> str:
return "ip_whitelist"
@property
def plugin_type(self) -> PluginType:
return PluginType.DOMAIN
def configure(self, config: dict) -> None:
if "enabled" in config:
self.enabled = str(config["enabled"]).lower() in ["true", "1", "yes"]
if "allowed_ips" in config:
ips_str = str(config["allowed_ips"])
self.allowed_ips = [ip.strip() for ip in ips_str.split(",") if ip.strip()]
if "status_code" in config:
try:
self.status_code = int(config["status_code"])
except ValueError:
self.status_code = 403
def process(self, context: PluginContext) -> PluginResult:
if not self.enabled or not self.allowed_ips:
return PluginResult()
ips_str = " ".join(self.allowed_ips)
haproxy_config = f"""# IP Whitelist - Only allow specific IPs
acl whitelisted_ip src {ips_str}
http-request deny deny_status {self.status_code} if !whitelisted_ip"""
return PluginResult(
haproxy_config=haproxy_config,
metadata={
"domain": context.domain,
"allowed_ips": self.allowed_ips,
"status_code": self.status_code
}
)
```
### Example 2: Cleanup Plugin (GLOBAL)
Perform cleanup tasks during each discovery cycle.
```python
import os
import sys
import glob
import time
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from plugins import PluginInterface, PluginType, PluginContext, PluginResult
from functions import logger_easyhaproxy
class CleanupPlugin(PluginInterface):
"""Plugin to perform cleanup tasks during discovery cycle"""
def __init__(self):
self.enabled = True
self.max_idle_time = 300 # 5 minutes
self.cleanup_temp_files = True
@property
def name(self) -> str:
return "cleanup"
@property
def plugin_type(self) -> PluginType:
return PluginType.GLOBAL
def configure(self, config: dict) -> None:
if "enabled" in config:
self.enabled = str(config["enabled"]).lower() in ["true", "1", "yes"]
if "max_idle_time" in config:
try:
self.max_idle_time = int(config["max_idle_time"])
except ValueError:
logger_easyhaproxy.warning(f"Invalid max_idle_time value: {config['max_idle_time']}, using default")
if "cleanup_temp_files" in config:
self.cleanup_temp_files = str(config["cleanup_temp_files"]).lower() in ["true", "1", "yes"]
def process(self, context: PluginContext) -> PluginResult:
if not self.enabled:
return PluginResult()
cleanup_actions = []
if self.cleanup_temp_files:
temp_dirs = ["/tmp", "/var/tmp"]
current_time = time.time()
for temp_dir in temp_dirs:
if not os.path.exists(temp_dir):
continue
try:
pattern = os.path.join(temp_dir, "easyhaproxy_*")
for filepath in glob.glob(pattern):
try:
file_age = current_time - os.path.getmtime(filepath)
if file_age > self.max_idle_time:
os.remove(filepath)
cleanup_actions.append(f"Removed old temp file: {filepath}")
except Exception as e:
logger_easyhaproxy.warning(f"Failed to remove temp file {filepath}: {e}")
except Exception as e:
logger_easyhaproxy.warning(f"Failed to cleanup {temp_dir}: {e}")
return PluginResult(
haproxy_config="",
metadata={
"actions_performed": len(cleanup_actions),
"actions": cleanup_actions
}
)
```
---
## Best Practices
1. **Use `initialize()` for resource setup** - Request directories/files during init, not in `process()`
2. **Use typed result fields** - Use `global_configs` and `defaults_configs` instead of metadata for config injection
3. **Handle errors gracefully** - Use try/except and return `PluginResult()` on error
4. **Validate in `configure()`** - Don't validate at `process()` time
5. **Use metadata for debugging** - Include useful info in `metadata` dict
6. **Support multiple boolean formats** - `str(config["enabled"]).lower() in ["true", "1", "yes"]`
7. **Support list and string formats** - Handle both YAML lists and comma-separated strings
8. **Use descriptive names** - Clear plugin name, ACL names, and config keys
9. **Document your plugin** - Include docstring with YAML and label examples
10. **Return empty result when disabled** - Check `self.enabled` first
---
## Testing Guidelines
### Unit Testing
```python
from plugins import PluginContext
from plugins.builtin.my_plugin import MyPlugin
class TestMyPlugin:
def test_plugin_initialization(self):
plugin = MyPlugin()
assert plugin.name == "my_plugin"
assert plugin.enabled is True
def test_plugin_generates_config(self):
plugin = MyPlugin()
plugin.configure({"my_setting": "test_value"})
context = PluginContext(
parsed_object={},
easymapping=[],
container_env={},
domain="example.com",
port="80",
host_config={}
)
result = plugin.process(context)
assert result.haproxy_config is not None
assert "X-My-Header test_value" in result.haproxy_config
def test_plugin_disabled(self):
plugin = MyPlugin()
plugin.configure({"enabled": "false"})
context = PluginContext(
parsed_object={}, easymapping=[], container_env={}, domain="example.com"
)
result = plugin.process(context)
assert result.haproxy_config == ""
```
---
## Troubleshooting
### Plugin Not Loading
1. File not in plugins directory: `ls -la /etc/easyhaproxy/plugins/`
2. Invalid Python syntax: `python3 -m py_compile /etc/easyhaproxy/plugins/my_plugin.py`
3. Class doesn't inherit `PluginInterface`
4. Missing required imports
### Plugin Not Executing
1. Plugin not enabled in configuration
2. Wrong plugin type for use case
3. Plugin disabled via configuration
### HAProxy Configuration Invalid
```bash
# Test configuration manually:
haproxy -c -f /etc/easyhaproxy/haproxy/haproxy.cfg
```
---
## Distribution
### Option 1: Single File
```bash
cp my_plugin.py /etc/easyhaproxy/plugins/
```
### Option 2: Docker Image with Plugin
```dockerfile
FROM byjg/easy-haproxy:latest
COPY my_plugin.py /app/src/plugins/builtin/
```
### Contributing to EasyHAProxy
1. Fork the repository
2. Add your plugin to `src/plugins/builtin/`
3. Add tests in `src/tests/test_plugins.py`
4. Add documentation in `docs/reference/plugins/`
5. Create a pull request
---
For more examples, see the builtin plugins in `/src/plugins/builtin/`:
- `cloudflare.py` - Simple DOMAIN plugin
- `fastcgi.py` - Advanced DOMAIN plugin with complex config
- `jwt_validator.py` - Security plugin with path-based logic
- `ip_whitelist.py` - Access control plugin
- `cleanup.py` - GLOBAL plugin example

338
docs/guides/plugins.md Normal file
View file

@ -0,0 +1,338 @@
---
sidebar_position: 3
sidebar_label: "Using Plugins"
---
# Using Plugins
EasyHAProxy supports a plugin system that extends HAProxy configuration with custom functionality. This guide explains how to use and configure plugins.
## What are Plugins?
Plugins automatically run during the discovery cycle and can:
- Add HAProxy configuration directives
- Perform maintenance tasks
- Modify discovery data
- Integrate with external services
## Plugin Types
### Global Plugins
Execute **once per discovery cycle** regardless of how many domains are discovered.
**Use cases:**
- Cleanup tasks
- Global monitoring
- DNS updates
- Log management
**Example:** `cleanup` plugin
### Domain Plugins
Execute **once for each discovered domain/host**.
**Use cases:**
- Domain-specific configuration
- IP restoration (Cloudflare)
- Path blocking
- Custom headers per domain
**Examples:** `cloudflare`, `deny_pages`
## Built-in Plugins
EasyHAProxy includes several built-in plugins ready to use:
- [Cloudflare](../reference/plugins/cloudflare.md) - Restore visitor IP from Cloudflare CDN
- [Cleanup](../reference/plugins/cleanup.md) - Cleanup temporary files
- [Deny Pages](../reference/plugins/deny-pages.md) - Block specific paths
- [IP Whitelist](../reference/plugins/ip-whitelist.md) - Restrict access to IPs/CIDR ranges
- [JWT Validator](../reference/plugins/jwt-validator.md) - Validate JWT tokens
- [FastCGI](../reference/plugins/fastcgi.md) - Configure PHP-FPM and FastCGI applications
## Configuration Methods
Plugins can be configured using different methods depending on your deployment environment:
### 1. Kubernetes Annotations (Ingress Resources)
Enable and configure domain plugins for specific Kubernetes ingresses:
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: easyhaproxy-ingress
# Enable plugins
easyhaproxy.plugins: "jwt_validator,deny_pages"
# Configure jwt_validator plugin (protect specific paths only)
easyhaproxy.plugin.jwt_validator.algorithm: "RS256"
easyhaproxy.plugin.jwt_validator.issuer: "https://auth.example.com/"
easyhaproxy.plugin.jwt_validator.audience: "https://api.example.com"
easyhaproxy.plugin.jwt_validator.pubkey_path: "/etc/easyhaproxy/jwt_keys/api_pubkey.pem"
easyhaproxy.plugin.jwt_validator.paths: "/api/admin,/api/users"
easyhaproxy.plugin.jwt_validator.only_paths: "false"
# Configure deny_pages plugin
easyhaproxy.plugin.deny_pages.paths: "/admin,/private"
easyhaproxy.plugin.deny_pages.status_code: "403"
name: api-ingress
namespace: production
spec:
rules:
- host: api.example.com
http:
paths:
- backend:
service:
name: api-service
port:
number: 8080
pathType: ImplementationSpecific
```
**Annotation format:**
- Enable plugins: `easyhaproxy.plugins: plugin1,plugin2`
- Configure plugin: `easyhaproxy.plugin.<plugin_name>.<config_key>: value`
See the [Kubernetes guide](../getting-started/kubernetes.md) for more examples.
### 2. Container Labels (Docker/Docker Compose)
Enable and configure domain plugins for specific Docker containers:
```yaml
services:
webapp:
image: myapp:latest
labels:
easyhaproxy.http.host: example.com
easyhaproxy.http.port: 80
# Enable multiple plugins
easyhaproxy.http.plugins: cloudflare,deny_pages
# Configure deny_pages plugin
easyhaproxy.http.plugin.deny_pages.paths: /admin,/api/internal
easyhaproxy.http.plugin.deny_pages.status_code: 403
```
**Label format:**
- Enable plugins: `easyhaproxy.<definition>.plugins: plugin1,plugin2`
- Configure plugin: `easyhaproxy.<definition>.plugin.<plugin_name>.<config_key>: value`
**Where `<definition>` is:** `http`, `https`, `tcp`, etc.
### 3. Static YAML Configuration
Configure plugins in `/etc/easyhaproxy/static/config.yaml`:
```yaml
plugins:
# Global settings
abort_on_error: false # Log and continue on errors (recommended)
# Enable GLOBAL plugins (run once per discovery cycle)
enabled: [cleanup]
# Configure plugins (both global and domain plugins)
config:
# Global plugin configuration (cleanup runs once per cycle)
cleanup:
enabled: true
max_idle_time: 600
# Domain plugin configuration (applies to ALL domains by default)
cloudflare:
enabled: true # Apply to all domains
use_builtin_ips: true # Use built-in Cloudflare IPs
# Domain plugin disabled by default (enable per-domain via labels/annotations)
deny_pages:
enabled: false
```
**Important distinctions:**
- **Global plugins** (like `cleanup`): Run once per discovery cycle, configured here only
- **Domain plugins** (like `cloudflare`, `deny_pages`, `jwt_validator`):
- Configuration here sets **defaults for ALL domains**
- Can be enabled/disabled per-domain via container labels or Kubernetes annotations
- Per-domain configuration overrides these defaults
### 4. Environment Variables
Configure plugins via environment variables. **Note:** Environment variables set system-wide defaults and cannot configure plugins per-domain.
```bash
# Enable GLOBAL plugins (run once per discovery cycle)
EASYHAPROXY_PLUGINS_ENABLED=cleanup
EASYHAPROXY_PLUGINS_ABORT_ON_ERROR=false
# Configure GLOBAL plugins
EASYHAPROXY_PLUGIN_CLEANUP_ENABLED=true
EASYHAPROXY_PLUGIN_CLEANUP_MAX_IDLE_TIME=600
# Configure DOMAIN plugins (sets defaults for ALL domains)
EASYHAPROXY_PLUGIN_CLOUDFLARE_ENABLED=true
EASYHAPROXY_PLUGIN_CLOUDFLARE_USE_BUILTIN_IPS=true
```
**Variable format:**
- Enable global plugins: `EASYHAPROXY_PLUGINS_ENABLED=plugin1,plugin2`
- Configure plugin: `EASYHAPROXY_PLUGIN_<PLUGIN_NAME>_<CONFIG_KEY>=value`
## Common Use Cases
### Protect API with JWT Authentication
**Secure entire API domain:**
```yaml
services:
api:
labels:
easyhaproxy.http.host: api.example.com
easyhaproxy.http.plugins: jwt_validator
easyhaproxy.http.plugin.jwt_validator.issuer: https://auth0.myapp.com/
easyhaproxy.http.plugin.jwt_validator.audience: https://api.example.com
easyhaproxy.http.plugin.jwt_validator.pubkey_path: /etc/easyhaproxy/jwt_keys/api_pubkey.pem
volumes:
- ./auth_pubkey.pem:/etc/easyhaproxy/jwt_keys/api_pubkey.pem:ro
```
### Restrict Admin Panel to Office IPs
```yaml
labels:
easyhaproxy.http.host: admin.example.com
easyhaproxy.http.plugins: ip_whitelist
easyhaproxy.http.plugin.ip_whitelist.allowed_ips: 203.0.113.0/24,198.51.100.42
```
### Protect Admin Paths
Block access to WordPress admin and other sensitive paths:
```yaml
labels:
easyhaproxy.http.host: wordpress.example.com
easyhaproxy.http.plugins: deny_pages
easyhaproxy.http.plugin.deny_pages.paths: /wp-admin,/wp-login.php,/.env
easyhaproxy.http.plugin.deny_pages.status_code: 404
```
### Cloudflare IP Restoration
```yaml
labels:
easyhaproxy.http.host: myapp.com
easyhaproxy.http.plugins: cloudflare
```
### Multiple Plugins Together
```yaml
labels:
easyhaproxy.http.host: secure-app.com
easyhaproxy.http.plugins: cloudflare,deny_pages
easyhaproxy.http.plugin.deny_pages.paths: /admin,/config
easyhaproxy.http.plugin.deny_pages.status_code: 403
```
### Automatic Cleanup
```yaml
# /etc/easyhaproxy/static/config.yaml
plugins:
enabled: [cleanup]
config:
cleanup:
enabled: true
max_idle_time: 3600 # 1 hour
```
## Error Handling
### Log and Continue (Recommended)
By default, plugin errors are logged as warnings and discovery continues:
```yaml
plugins:
abort_on_error: false # Default
```
### Abort on Error
Stop discovery cycle if any plugin fails:
```yaml
plugins:
abort_on_error: true
```
## Troubleshooting
### Enable Debug Logging
```bash
EASYHAPROXY_LOG_LEVEL=DEBUG
```
**Look for:**
```
INFO: Loaded builtin plugin: cloudflare (domain)
INFO: Loaded builtin plugin: cleanup (global)
DEBUG: Executing domain plugin: cloudflare for domain: example.com
DEBUG: Plugin cloudflare metadata: {'domain': 'example.com', 'ip_list_path': '/etc/easyhaproxy/cloudflare_ips.lst'}
```
### Plugin Not Loading
**Check:**
1. Plugin file exists in `/etc/easyhaproxy/plugins/` or builtin directory
2. Python syntax is valid
3. Plugin class inherits from `PluginInterface`
4. Check logs for load errors
### Configuration Not Applied
**Check precedence order:**
For Kubernetes deployments:
1. Ingress annotations (highest)
2. YAML configuration
3. Environment variables (lowest)
For Docker deployments:
1. Container labels (highest)
2. YAML configuration
3. Environment variables (lowest)
## Best Practices
1. **Start with log-and-continue mode** - Use `abort_on_error: false` until you're confident plugins are stable
2. **Use container labels for domain-specific config** - Easier to manage per-service
3. **Use YAML/env for global config** - Better for global plugins and defaults
4. **Enable debug logging during testing** - Helps identify configuration issues
5. **Test plugin changes in staging first** - Avoid production surprises
## Limitations
- Plugins must be written in Python
- Domain plugins execute for each domain, so keep them lightweight
- Plugins cannot modify the Jinja2 template structure directly
- Plugin errors in abort mode prevent all configuration updates
## Creating Custom Plugins
Want to create your own plugins? See the [Plugin Developer Guide](plugin-development.md) for detailed instructions on building custom plugins that extend EasyHAProxy functionality.
## Further Reading
- [Plugin Developer Guide](plugin-development.md) - Create custom plugins
- [Container Labels](../reference/container-labels.md) - Label configuration reference
- [Environment Variables](../reference/environment-variables.md) - Environment variable reference
- [Static Configuration](../getting-started/static.md) - YAML configuration reference
- [Kubernetes Guide](../getting-started/kubernetes.md) - Using plugins with Kubernetes

145
docs/guides/ssl.md Normal file
View file

@ -0,0 +1,145 @@
---
sidebar_position: 1
sidebar_label: "Custom SSL Certificates"
---
# Setup custom certificates
You can use your own certificates with EasyHAProxy instead of (or in addition to) automatic ACME/Certbot certificates.
:::info How SSL Termination Works
SSL termination happens at the **HAProxy level**, NOT in your backend containers.
- Your backend containers should **only** expose HTTP (port 80), not HTTPS
- HAProxy handles all SSL/TLS encryption and decryption
- Backend containers receive plain HTTP traffic from HAProxy
- Do NOT configure SSL in your backend application when using EasyHAProxy
This is the **correct design** - it centralizes SSL management at the proxy layer.
:::
:::info Certificate Types
EasyHAProxy supports two certificate sources:
- **ACME/Certbot automatic certificates** - Issued automatically via Let's Encrypt or other ACME providers (see [ACME documentation](./acme.md))
- **Manual/custom certificates** - Your own certificates loaded via volume mount (recommended) or labels (this page)
Both can be used simultaneously. Per domain, ACME certificates (if `certbot=true` label is set) take precedence over manual certificates.
:::
There are two ways to provide custom certificates:
- [Map the certificate as a docker volume](#map-the-certificate-as-a-docker-volume)
- [Setup certificate as a label definition](#setup-certificate-as-a-label-definition-in-docker-container)
## Map the certificate as a docker volume
EasyHAProxy stores the certificates inside the container folder `/etc/easyhaproxy/certs/haproxy`.
1. Run EasyHAProxy with the volume for the certificates:
```bash title="Create and mount certificate volume"
docker volume create certs_haproxy
docker run \
/* other parameters */
-v certs_haproxy:/etc/easyhaproxy/certs/haproxy \
-d byjg/easy-haproxy
```
2. Create a single PEM from the certificate and the key.
```bash title="Combine certificate and key"
cat example.com.crt example.com.key > single.pem
cat single.pem
-----BEGIN CERTIFICATE-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC5ZheHqmBnEJP+
U9r1gxYWKLzdqrMrcxtQN6M1hIH9n0peuJeIrybdcV7sMbStMXI=
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
MIIEojCCA4qgAwIBAgIUegW2BimwuL4RzRZ2WYkHA6U5nkAwDQYJKoZIhvcNAQEL
3j4wz8/I5fdsk090j4s5KA==
-----END PRIVATE KEY-----
```
3. Copy this certificate to EasyHAProxy volume:
```bash title="Copy certificate to container"
# IMPORTANT: Filename must match the domain!
docker cp single.pem easyhaproxy:/etc/easyhaproxy/certs/haproxy/example.com.pem
```
:::warning Important Notes
- The filename **must match the domain name**: `example.com.pem` for domain `example.com`
- When using volume-mounted certificates, **do NOT** use the `easyhaproxy.[definition].sslcert` label
- The volume mount method and the label method are **mutually exclusive** per domain
- SSL termination happens at HAProxy - your backend containers should only serve HTTP
:::
4. Configure your backend container (no sslcert label needed):
```yaml
services:
easyhaproxy:
image: byjg/easy-haproxy:6.0.0
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- certs_haproxy:/etc/easyhaproxy/certs/haproxy
ports:
- "80:80"
- "443:443"
myapp:
image: nginx
labels:
easyhaproxy.web.host: example.com
easyhaproxy.web.port: 80 # Frontend port (HAProxy listens here)
easyhaproxy.web.localport: 80 # Backend port (your container)
# NO sslcert label when using volume method!
volumes:
certs_haproxy:
```
## Setup certificate as a label definition in docker container
:::info Alternative Method
This method embeds certificates directly in container labels. Use it when you want certificates in version control or don't want to manage external files. **Volume method is recommended for most use cases.**
:::
1. Create a single PEM from the certificate and key:
```bash title="Combine certificate and key"
cat example.com.crt example.com.key > single.pem
```
2. Convert the `single.pem` to BASE64 in a single line:
```bash title="Convert to BASE64"
cat single.pem | base64 -w0
```
3. Add the Base64 string to your container label:
```yaml
services:
myapp:
image: nginx
labels:
easyhaproxy.web.host: example.com
easyhaproxy.web.port: 80
easyhaproxy.web.localport: 80
easyhaproxy.web.sslcert: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t..." # Base64 certificate
```
:::warning When Using Label Method
- **There is no necessary to** mount the `/etc/easyhaproxy/certs/haproxy` volume for this domain
- Using `sslcert` label means the volume-mounted certificate will be **ignored**
- Certificate is visible in `docker inspect` output (less secure)
- Updating requires container redeployment
:::
----
[Open source ByJG](http://opensource.byjg.com)