Sometimes it is useful to quickly access your Flask application running on localhost from another device or location for testing purposes. In this article I'll show you how to use the pyngrok package to provision a temporary public URL for your application that works from your phone or from anywhere in the world!
The Ngrok Command
If you want to try this out with an application of yours, go into your application directory and activate its virtual environment. For the examples in this article I will be using the microblog application featured in my Flask Mega-Tutorial.
To begin, install pyngrok into your virtual environment:
(venv) $ pip install pyngrok
Pyngrok is a Python-friendly wrapper for ngrok. It downloads and installs a copy of the ngrok binary for your platform the first time you use it. Run
ngrok --help with your virtual environment activated to confirm that you have it installed and working:
(venv) $ ngrok --help
I will show you how to integrate ngrok with your Flask application in the next section, but for now let's learn how it works by running it in standalone mode. You will need two terminal windows for this, both with the virtual environment activated.
On the first terminal, start your application. Normally you would do it with the
flask run command, but if you start your application by running a Python script that is fine too. What matters is that your application listens for requests at http://localhost:5000 (or a different port if you like).
Now go to your second terminal and start ngrok as follows:
(venv) $ ngrok http 5000
If you use a port other than 5000, then adjust the command accordingly. This is going to create a tunnel between a randomly generated public URL in the ngrok.io domain and your application running on localhost. This is the output of ngrok:
Look for the two lines that begin with the word "Forwarding" to know your URL. These two lines show the http:// and https:// versions of your URL. In the example above the URL that I was given was https://3bb2431328e0.ngrok.io. You will get a similar one, but the subdomain portion is going to be different each time you run ngrok. While the ngrok process is running (limited to a maximum of eight hours) any requests that are sent to this URL are immediately forwarded to your application.
Send the URL to your phone and open it in your mobile browser to see how cool ngrok is. You can also send the URL to a friend if you like, as it works from anywhere in the world.
Create an Ngrok Tunnel from Python Code
If you've never used ngrok before, I'm sure by now you are excited about all the possibilities it opens. In this section I'm going to show you an improved workflow that uses the Python access into ngrok provided by the pyngrok package. This package can start the tunnel automatically when the application starts.
To open an ngrok tunnel I've added a
start_ngrok() function to my application. For my microblog example, I put this function in the top-level microblog.py module, but it can really go anywhere you like. Here is the code for this function:
def start_ngrok(): from pyngrok import ngrok url = ngrok.connect(5000).public_url print(' * Tunnel URL:', url)
This function performs the same function as the
ngrok http 5000 command from the previous section. The
ngrok.connect().public_url expression returns the public URL that was assigned to the tunnel. My
start_ngrok() function prints this URL to the terminal, so that I can then send it to my phone (or a friend) as needed.
To decide wether I want to start my application with a tunnel or not I'm going to use a
START_NGROK configuration option. When the option is enabled I will call the above function to get a tunnel set up. I've added the following conditional to the main script in my application:
if app.config['START_NGROK']: start_ngrok()
Finally, I've added the
START_NGROK configuration option to my
Config class, which I have in the config.py module:
class Config: # ... START_NGROK = os.environ.get('START_NGROK') is not None
START_NGROK option is going to be set to
True whenever an environment variable with the same name is set. The value that is set in the variable does not really matter, as long as a non-empty value is set I'll consider the variable enabled.
Now I can set the environment variable before I start my application if I want to use ngrok:
(venv) $ START_NGROK=1 flask run * Serving Flask app "microblog.py" * Environment: production WARNING: Do not use the development server in a production environment. Use a production WSGI server instead. * Debug mode: off * Tunnel URL: http://24d1fd7fd908.ngrok.io * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
And here you can see that in the line before last I'm showing the ngrok URL.
If you use the Flask reloader and enable the ngrok tunnel you will notice that two tunnels are started. This is because the reloader runs two processes: a parent process that monitors files for changes, and a child process that runs the actual server. The parent process kills and restarts the child process every time it detects changes to a source file.
It would be better if the tunnel could be created only in the parent process, which is the process that has a longer life. That would mean that a reload event would preserve the tunnel URL, since it is associated with the parent reloader process. So a nice improvement would be to make sure the
START_NGROK configuration variable is always
False in the child process.
Looking at the internals of the Flask reloader (which is actually in the Werkzeug package), I noticed that when the child process is launched, a
WERKZEUG_RUN_MAIN variable is set to the string
'true' in the environment. This is used by Werkzeug to detect wether a process is the parent or the child. I can check on this variable and make sure
START_NGROK is always
False for the child process:
class Config: # ... START_NGROK = os.environ.get('START_NGROK') is not None and \ os.environ.get('WERKZEUG_RUN_MAIN') is not 'true'
So now, when the main Flask process starts it will set up the tunnel. If the reloader is enabled, then a child process will be launched with
WERKZEUG_RUN_MAIN=true in the environment, so there will be no second tunnel started. And every time the child process is recycled the tunnel set up in the parent process will be unaffected, so the same tunnel URL will remain valid for as long as the reloader process is running, up to a maximum of eight hours.
I hope you incorporate ngrok into your development workflow. I have used it for many years to run quick tests on my applications from other devices.
You should keep in mind that ngrok is a service intended for very low traffic, so it is not a deployment mechanism but just a convenient tool to run quick tests. If you end up become a frequent user of this tool, consider one of their paid plans, all of which allow you to secure a permanent URL.