diff --git a/reflex/config.py b/reflex/config.py index 21614b9b1..233087938 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -562,6 +562,12 @@ class EnvironmentVariables: # Whether to run the frontend only. Exclusive with REFLEX_BACKEND_ONLY. REFLEX_FRONTEND_ONLY: EnvVar[bool] = env_var(False) + # The port to run the frontend on. + REFLEX_FRONTEND_PORT: EnvVar[int | None] = env_var(None) + + # The port to run the backend on. + REFLEX_BACKEND_PORT: EnvVar[int | None] = env_var(None) + # Reflex internal env to reload the config. RELOAD_CONFIG: EnvVar[bool] = env_var(False, internal=True) @@ -640,19 +646,21 @@ class Config(Base): loglevel: constants.LogLevel = constants.LogLevel.DEFAULT # The port to run the frontend on. NOTE: When running in dev mode, the next available port will be used if this is taken. - frontend_port: int = constants.DefaultPorts.FRONTEND_PORT + frontend_port: int | None = None # The path to run the frontend on. For example, "/app" will run the frontend on http://localhost:3000/app frontend_path: str = "" # The port to run the backend on. NOTE: When running in dev mode, the next available port will be used if this is taken. - backend_port: int = constants.DefaultPorts.BACKEND_PORT + backend_port: int | None = None # The backend url the frontend will connect to. This must be updated if the backend is hosted elsewhere, or in production. - api_url: str = f"http://localhost:{backend_port}" + api_url: str = f"http://localhost:{constants.DefaultPorts.BACKEND_PORT}" # The url the frontend will be hosted on. - deploy_url: Optional[str] = f"http://localhost:{frontend_port}" + deploy_url: Optional[str] = ( + f"http://localhost:{constants.DefaultPorts.FRONTEND_PORT}" + ) # The url the backend will be hosted on. backend_host: str = "0.0.0.0" diff --git a/reflex/reflex.py b/reflex/reflex.py index e4be0c89a..410485551 100644 --- a/reflex/reflex.py +++ b/reflex/reflex.py @@ -127,8 +127,8 @@ def _run( env: constants.Env = constants.Env.DEV, frontend: bool = True, backend: bool = True, - frontend_port: int = config.frontend_port, - backend_port: int = config.backend_port, + frontend_port: int | None = None, + backend_port: int | None = None, backend_host: str = config.backend_host, loglevel: constants.LogLevel = config.loglevel, ): @@ -158,17 +158,28 @@ def _run( # Find the next available open port if applicable. if frontend: + auto_increment_frontend = not bool(frontend_port or config.frontend_port) frontend_port = processes.handle_port( "frontend", - frontend_port, - constants.DefaultPorts.FRONTEND_PORT, + ( + frontend_port + or config.frontend_port + or constants.DefaultPorts.FRONTEND_PORT + ), + auto_increment=auto_increment_frontend, ) if backend: + auto_increment_backend = not bool(backend_port or config.backend_port) + backend_port = processes.handle_port( "backend", - backend_port, - constants.DefaultPorts.BACKEND_PORT, + ( + backend_port + or config.backend_port + or constants.DefaultPorts.BACKEND_PORT + ), + auto_increment=auto_increment_backend, ) # Apply the new ports to the config. @@ -246,7 +257,7 @@ def _run( # Start the frontend and backend. with processes.run_concurrently_context(*commands): # In dev mode, run the backend on the main thread. - if backend and env == constants.Env.DEV: + if backend and backend_port and env == constants.Env.DEV: backend_cmd( backend_host, int(backend_port), loglevel.subprocess_level(), frontend ) @@ -275,10 +286,14 @@ def run( envvar=environment.REFLEX_BACKEND_ONLY.name, ), frontend_port: int = typer.Option( - config.frontend_port, help="Specify a different frontend port." + config.frontend_port, + help="Specify a different frontend port.", + envvar=environment.REFLEX_FRONTEND_PORT.name, ), backend_port: int = typer.Option( - config.backend_port, help="Specify a different backend port." + config.backend_port, + help="Specify a different backend port.", + envvar=environment.REFLEX_BACKEND_PORT.name, ), backend_host: str = typer.Option( config.backend_host, help="Specify the backend host." diff --git a/reflex/utils/processes.py b/reflex/utils/processes.py index c92fb7d1a..a0c13300d 100644 --- a/reflex/utils/processes.py +++ b/reflex/utils/processes.py @@ -116,17 +116,14 @@ def change_port(port: int, _type: str) -> int: return new_port -def handle_port(service_name: str, port: int, default_port: int) -> int: +def handle_port(service_name: str, port: int, auto_increment: bool) -> int: """Change port if the specified port is in use and is not explicitly specified as a CLI arg or config arg. - otherwise tell the user the port is in use and exit the app. - - We make an assumption that when port is the default port,then it hasn't been explicitly set since its not straightforward - to know whether a port was explicitly provided by the user unless its any other than the default. + Otherwise tell the user the port is in use and exit the app. Args: service_name: The frontend or backend. port: The provided port. - default_port: The default port number associated with the specified service. + auto_increment: Whether to automatically increment the port. Returns: The port to run the service on. @@ -134,13 +131,15 @@ def handle_port(service_name: str, port: int, default_port: int) -> int: Raises: Exit:when the port is in use. """ - if is_process_on_port(port): - if port == int(default_port): - return change_port(port, service_name) - else: - console.error(f"{service_name.capitalize()} port: {port} is already in use") - raise typer.Exit() - return port + if (process := get_process_on_port(port)) is None: + return port + if auto_increment: + return change_port(port, service_name) + else: + console.error( + f"{service_name.capitalize()} port: {port} is already in use by PID: {process.pid}." + ) + raise typer.Exit() def new_process(