feat: Enable SSL flags for local development (#7060)

* ssl-support: Enable SSL for local development to simulate a production-like environment. This would allow testing features requiring SSL during development, third party integrations, and allowing easier SSL integration without WAFs and reverse proxies

* rebase

* rebase fixes

---------

Co-authored-by: lpetkov <lpetkov@pros.com>
Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>
Co-authored-by: Jordan Frazier <122494242+jordanrfrazier@users.noreply.github.com>
Co-authored-by: Jordan Frazier <jordan.frazier@datastax.com>
This commit is contained in:
Lilko Petkov 2025-04-10 21:12:04 +03:00 committed by GitHub
commit 58b9551758
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 33 additions and 12 deletions

View file

@ -122,6 +122,8 @@ python -m langflow run [OPTIONS]
| <Link id="run-auto-saving-interval"/>`--auto-saving-interval` | `1000` | Integer | Set the interval for flow auto-saving in milliseconds.<br/>See [`LANGFLOW_AUTO_SAVING_INTERVAL` variable](./environment-variables.md#LANGFLOW_AUTO_SAVING_INTERVAL). |
| <Link id="run-health-check-max-retries"/>`--health-check-max-retries` | `5` | Integer | Set the maximum number of retries for the health check. Use `--no-health-check-max-retries` to disable the maximum number of retries for the health check.<br/>See [`LANGFLOW_HEALTH_CHECK_MAX_RETRIES` variable](./environment-variables.md#LANGFLOW_HEALTH_CHECK_MAX_RETRIES). |
| <Link id="run-max-file-size-upload"/>`--max-file-size-upload` | `100` | Integer | Set the maximum file size for the upload in megabytes.<br/>See [`LANGFLOW_MAX_FILE_SIZE_UPLOAD` variable](./environment-variables.md#LANGFLOW_MAX_FILE_SIZE_UPLOAD). |
| <Link id="run-ssl-cert-file-path"/>`--ssl-cert-file-path` | Not set | String | Path to the SSL certificate file on the local system. |
| <Link id="run-ssl-key-file-path"/>`--ssl-key-file-path` | Not set | String | Path to the SSL key file on the local system. |
| <Link id="run-help"/>`--help` | *Not applicable* | *Not applicable* | Display information about the command usage and its options and arguments. |
### langflow superuser

View file

@ -226,9 +226,11 @@ The following table lists the environment variables supported by Langflow.
| <Link id="LANGFLOW_LOAD_FLOWS_PATH"/><span class="env-prefix">LANGFLOW_</span>LOAD_FLOWS_PATH | String | Not set | Path to a directory containing flow JSON files to be loaded on startup. Note that this feature only works if <span class="env-prefix">LANGFLOW_</span>AUTO_LOGIN is enabled. |
| <Link id="LANGFLOW_WORKER_TIMEOUT"/><span class="env-prefix">LANGFLOW_</span>WORKER_TIMEOUT | Integer | `300` | Worker timeout in seconds.<br/>See [`--worker-timeout` option](./configuration-cli.md#run-worker-timeout). |
| <Link id="LANGFLOW_WORKERS"/><span class="env-prefix">LANGFLOW_</span>WORKERS | Integer | `1` | Number of worker processes.<br/>See [`--workers` option](./configuration-cli.md#run-workers). |
| <Link id="LANGFLOW_SSL_CERT_FILE"/><span class="env-prefix">LANGFLOW_</span>SSL_CERT_FILE | String | Not set | Path to the SSL certificate file on the local system. |
| <Link id="LANGFLOW_SSL_KEY_FILE"/><span class="env-prefix">LANGFLOW_</span>SSL_KEY_FILE | String | Not set | Path to the SSL key file on the local system. |
</div>
## Configure .env, override.conf, and tasks.json files
The following examples show how to configure Langflow using environment variables in different scenarios.

View file

@ -161,6 +161,10 @@ def run(
help="Defines the polling interval for the webhook.",
show_default=False,
),
ssl_cert_file_path: str | None = typer.Option(
None, help="Defines the SSL certificate file path.", show_default=False
),
ssl_key_file_path: str | None = typer.Option(None, help="Defines the SSL key file path.", show_default=False),
) -> None:
"""Run Langflow."""
# Register SIGTERM handler
@ -202,6 +206,8 @@ def run(
log_level = settings_service.settings.log_level
frontend_path = settings_service.settings.frontend_path
backend_only = settings_service.settings.backend_only
ssl_cert_file_path = settings_service.settings.ssl_cert_file if ssl_cert_file_path is None else ssl_cert_file_path
ssl_key_file_path = settings_service.settings.ssl_key_file if ssl_key_file_path is None else ssl_key_file_path
# create path object if frontend_path is provided
static_files_dir: Path | None = Path(frontend_path) if frontend_path else None
@ -215,7 +221,10 @@ def run(
"bind": f"{host}:{port}",
"workers": get_number_of_workers(workers),
"timeout": worker_timeout,
"certfile": ssl_cert_file_path,
"keyfile": ssl_key_file_path,
}
protocol = "https" if options["keyfile"] and options["certfile"] else "http"
# Define an env variable to know if we are just testing the server
if "pytest" in sys.modules:
@ -226,10 +235,10 @@ def run(
# Run using uvicorn on MacOS and Windows
# Windows doesn't support gunicorn
# MacOS requires an env variable to be set to use gunicorn
run_on_windows(host, port, log_level, options, app)
run_on_windows(host, port, log_level, options, app, protocol)
else:
# Run using gunicorn on Linux
process = run_on_mac_or_linux(host, port, log_level, options, app)
process = run_on_mac_or_linux(host, port, log_level, options, app, protocol)
if open_browser and not backend_only:
click.launch(f"http://{host}:{port}")
if process:
@ -250,12 +259,14 @@ def run(
raise typer.Exit(1) from e
def wait_for_server_ready(host, port) -> None:
def wait_for_server_ready(host, port, protocol) -> None:
"""Wait for the server to become ready by polling the health endpoint."""
status_code = 0
while status_code != httpx.codes.OK:
try:
status_code = httpx.get(f"http://{host}:{port}/health").status_code
status_code = httpx.get(
f"{protocol}://{host}:{port}/health", verify=host not in ("127.0.0.1", "localhost")
).status_code
except HTTPError:
time.sleep(1)
except Exception: # noqa: BLE001
@ -263,18 +274,18 @@ def wait_for_server_ready(host, port) -> None:
time.sleep(1)
def run_on_mac_or_linux(host, port, log_level, options, app):
def run_on_mac_or_linux(host, port, log_level, options, app, protocol):
webapp_process = Process(target=run_langflow, args=(host, port, log_level, options, app))
webapp_process.start()
wait_for_server_ready(host, port)
wait_for_server_ready(host, port, protocol)
print_banner(host, port)
print_banner(host, port, protocol)
return webapp_process
def run_on_windows(host, port, log_level, options, app) -> None:
def run_on_windows(host, port, log_level, options, app, protocol) -> None:
"""Run the Langflow server on Windows."""
print_banner(host, port)
print_banner(host, port, protocol)
run_langflow(host, port, log_level, options, app)
@ -358,7 +369,7 @@ def stylize_text(text: str, to_style: str, *, is_prerelease: bool) -> str:
return text.replace(to_style, styled_text)
def print_banner(host: str, port: int) -> None:
def print_banner(host: str, port: int, protocol: str) -> None:
notices = []
package_names = [] # Track package names for pip install instructions
is_pre_release = False # Track if any package is a pre-release
@ -398,7 +409,7 @@ def print_banner(host: str, port: int) -> None:
"We collect anonymous usage data to improve Langflow.\n"
"You can opt-out by setting [bold]DO_NOT_TRACK=true[/bold] in your environment."
)
access_link = f"Access [link=http://{host}:{port}]http://{host}:{port}[/link]"
access_link = f"Access [link={protocol}://{host}:{port}]{protocol}://{host}:{port}[/link]"
panel_content = "\n\n".join([title, *styled_notices, info_text, telemetry_text, access_link])
panel = Panel(panel_content, box=box.ROUNDED, border_style="blue", expand=False)
@ -416,6 +427,8 @@ def run_langflow(host, port, log_level, options, app) -> None:
port=port,
log_level=log_level.lower(),
loop="asyncio",
ssl_keyfile=options["keyfile"],
ssl_certfile=options["certfile"],
)
else:
from langflow.server import LangflowApplication

View file

@ -224,6 +224,10 @@ class Settings(BaseSettings):
"""The polling interval for the webhook in ms."""
fs_flows_polling_interval: int = 10000
"""The polling interval in milliseconds for synchronizing flows from the file system."""
ssl_cert_file: str | None = None
"""Path to the SSL certificate file on the local system."""
ssl_key_file: str | None = None
"""Path to the SSL key file on the local system."""
# MCP Server
mcp_server_enabled: bool = True