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-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-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-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. | | <Link id="run-help"/>`--help` | *Not applicable* | *Not applicable* | Display information about the command usage and its options and arguments. |
### langflow superuser ### 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_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_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_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> </div>
## Configure .env, override.conf, and tasks.json files ## Configure .env, override.conf, and tasks.json files
The following examples show how to configure Langflow using environment variables in different scenarios. 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.", help="Defines the polling interval for the webhook.",
show_default=False, 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: ) -> None:
"""Run Langflow.""" """Run Langflow."""
# Register SIGTERM handler # Register SIGTERM handler
@ -202,6 +206,8 @@ def run(
log_level = settings_service.settings.log_level log_level = settings_service.settings.log_level
frontend_path = settings_service.settings.frontend_path frontend_path = settings_service.settings.frontend_path
backend_only = settings_service.settings.backend_only 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 # create path object if frontend_path is provided
static_files_dir: Path | None = Path(frontend_path) if frontend_path else None static_files_dir: Path | None = Path(frontend_path) if frontend_path else None
@ -215,7 +221,10 @@ def run(
"bind": f"{host}:{port}", "bind": f"{host}:{port}",
"workers": get_number_of_workers(workers), "workers": get_number_of_workers(workers),
"timeout": worker_timeout, "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 # Define an env variable to know if we are just testing the server
if "pytest" in sys.modules: if "pytest" in sys.modules:
@ -226,10 +235,10 @@ def run(
# Run using uvicorn on MacOS and Windows # Run using uvicorn on MacOS and Windows
# Windows doesn't support gunicorn # Windows doesn't support gunicorn
# MacOS requires an env variable to be set to use 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: else:
# Run using gunicorn on Linux # 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: if open_browser and not backend_only:
click.launch(f"http://{host}:{port}") click.launch(f"http://{host}:{port}")
if process: if process:
@ -250,12 +259,14 @@ def run(
raise typer.Exit(1) from e 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.""" """Wait for the server to become ready by polling the health endpoint."""
status_code = 0 status_code = 0
while status_code != httpx.codes.OK: while status_code != httpx.codes.OK:
try: 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: except HTTPError:
time.sleep(1) time.sleep(1)
except Exception: # noqa: BLE001 except Exception: # noqa: BLE001
@ -263,18 +274,18 @@ def wait_for_server_ready(host, port) -> None:
time.sleep(1) 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 = Process(target=run_langflow, args=(host, port, log_level, options, app))
webapp_process.start() 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 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.""" """Run the Langflow server on Windows."""
print_banner(host, port) print_banner(host, port, protocol)
run_langflow(host, port, log_level, options, app) 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) 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 = [] notices = []
package_names = [] # Track package names for pip install instructions package_names = [] # Track package names for pip install instructions
is_pre_release = False # Track if any package is a pre-release 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" "We collect anonymous usage data to improve Langflow.\n"
"You can opt-out by setting [bold]DO_NOT_TRACK=true[/bold] in your environment." "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_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) 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, port=port,
log_level=log_level.lower(), log_level=log_level.lower(),
loop="asyncio", loop="asyncio",
ssl_keyfile=options["keyfile"],
ssl_certfile=options["certfile"],
) )
else: else:
from langflow.server import LangflowApplication from langflow.server import LangflowApplication

View file

@ -224,6 +224,10 @@ class Settings(BaseSettings):
"""The polling interval for the webhook in ms.""" """The polling interval for the webhook in ms."""
fs_flows_polling_interval: int = 10000 fs_flows_polling_interval: int = 10000
"""The polling interval in milliseconds for synchronizing flows from the file system.""" """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
mcp_server_enabled: bool = True mcp_server_enabled: bool = True