Flask is a simple Python web framework based on Werkzeug, Jinja 2 and "good intentions." Vultr is relatively new cloud provider (with tons of experience) that offers high-performance SSD servers in datacenters around the world. Its simple UI makes it easy to use and love.
In this article, I'm going to walk through deploying a Flask application to Vultr using Nanobox. Nanobox uses Docker to build local development and staging environments, as well as scalable, highly-available production environments on Vultr.
Setup Your Flask Project
Whether you have an existing Flask project or are starting the scratch, the process of configuring it for Nanobox is the same.
Add a boxfile.yml
Nanobox uses the
boxfile.yml to build and configure your app's environment both locally and in production. Create a
boxfile.yml in the root of your project with the following:
run.config: engine: python extra_packages: - nginx web.main: start: nginx: nginx -c /app/etc/nginx.conf flask: gunicorn -c /app/etc/gunicorn.py app:app data.db: image: nanobox/postgresql:9.5
This includes everything Flask needs to run. You may need to update a few items specific to your project (more in the docs and guides), but in this walk-through, I'm going to use:
- A Python runtime.
- A web component running a gunicorn web server and an Nginx proxy.
- A Postgres database.
Start the Local Dev Environment
boxfile.yml in place, you can fire up a virtualized local development environment. I recommend adding a DNS alias just so the app will be easier to access from a browser.
# Add a convenient way to access the app from a browser nanobox dns add local flask.local # Start the dev environment nanobox run
Nanobox will provision a local development environment, spin up a containerized Postgres database, mount your local codebase into the VM, load your app's dependencies, then drop you into a console inside the VM.
Install Flask & Create an App
If you have an existing Flask project, you can skip this section. If not, first install Flask and all its requirements.
# Install Flask pip install Flask # Freeze the pip modules into the requirements.txt pip freeze > requirements.txt
Create a Flask app in the root of your project named
app.py. Below is just a simple "Hello Nanobox!" example.
from flask import Flask app = Flask(__name__) @app.route("/") def hello(): return "Hello Nanobox!"
Your project's current working directory is mounted into the
/app directory in the VM, so all the Flask files written there will propagate up to your VM and vice versa.
In order for requests to make it to Flask running with Nanobox, Flask needs to run on
0.0.0.0. Add the following to your
if __name__ == "__main__": app.run(host='0.0.0.0')
Update the Database Connection
When Nanobox spins up a Postgres database, it generates environment variables for the necessary connection credentials. Flask is pretty free-form when it comes to configuring a database connection. However you choose to configure yours, you can access the following environment variables:
import os host = os.environ.get('DATA_DB_HOST') user = os.environ.get('DATA_DB_USER') passwd = os.environ.get('DATA_DB_PASS')
Nanobox also provides a default database named
gonano, but you're welcome to create your own databases.
If you don't already have
psycopg2, the Python-Postgres adapter, in your
requirements.txt, you'll need to install it. From the
/app directory in your Nanobox console:
# Install your psycopg2 pip install psycopg2 # Freeze your requirements.txt pip freeze > requirements.txt
Run Flask Locally
app.py configured and database connection updated, you're ready to start Flask in your local dev environment.
You'll then be able to access your running Flask app at flask.local:5000.
You don't need to use Nginx or gunicorn when running Flask locally. Those will just be used when the app is deployed.
Whenever you exit out of the Nanobox console, it'll shut down your VM and drop you back into your host OS.
Prepare Flask for Deploy
Before you deploy the project, you need to make sure
gunicorn is installed and include Nginx and gunicorn config files to use when deployed.
If you don't already have
gunicorn in your
requirements.txt, you'll need to install it. From the root of your project:
# Start the local dev environment nanobox run # Install gunicorn pip install gunicorn # Freeze dependencies pip freeze > requirements.txt # Exit Nanobox exit
Add Nginx & gunicorn Config Files
Create two files in your project:
# Server mechanics bind = '0.0.0.0:8000' backlog = 2048 daemon = False pidfile = None umask = 0 user = None group = None tmp_upload_dir = None proc_name = None # Logging errorlog = '-' loglevel = 'info' accesslog = '-' access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"' # # Worker processes # # workers - The number of worker processes that this server # should keep alive for handling requests. # # A positive integer generally in the 2-4 x $(NUM_CORES) # range. You'll want to vary this a bit to find the best # for your particular application's work load. # # worker_class - The type of workers to use. The default # sync class should handle most 'normal' types of work # loads. You'll want to read # http://docs.gunicorn.org/en/latest/design.html#choosing-a-worker-type # for information on when you might want to choose one # of the other worker classes. # # An string referring to a 'gunicorn.workers' entry point # or a python path to a subclass of # gunicorn.workers.base.Worker. The default provided values # are: # # egg:gunicorn#sync # egg:gunicorn#eventlet - Requires eventlet >= 0.9.7 # egg:gunicorn#gevent - Requires gevent >= 0.12.2 (?) # egg:gunicorn#tornado - Requires tornado >= 0.2 # # worker_connections - For the eventlet and gevent worker classes # this limits the maximum number of simultaneous clients that # a single process can handle. # # A positive integer generally set to around 1000. # # timeout - If a worker does not notify the master process in this # number of seconds it is killed and a new worker is spawned # to replace it. # # Generally set to thirty seconds. Only set this noticeably # higher if you're sure of the repercussions for sync workers. # For the non sync workers it just means that the worker # process is still communicating and is not tied to the length # of time required to handle a single request. # # keepalive - The number of seconds to wait for the next request # on a Keep-Alive HTTP connection. # # A positive integer. Generally set in the 1-5 seconds range. # workers = 1 worker_class = 'sync' worker_connections = 1000 timeout = 30 keepalive = 2 spew = False # # Server hooks # # post_fork - Called just after a worker has been forked. # # A callable that takes a server and worker instance # as arguments. # # pre_fork - Called just prior to forking the worker subprocess. # # A callable that accepts the same arguments as after_fork # # pre_exec - Called just prior to forking off a secondary # master process during things like config reloading. # # A callable that takes a server instance as the sole argument. # def post_fork(server, worker): server.log.info("Worker spawned (pid: %s)", worker.pid) def pre_fork(server, worker): pass def pre_exec(server): server.log.info("Forked child, re-executing.") def when_ready(server): server.log.info("Server is ready. Spawning workers") def worker_int(worker): worker.log.info("worker received INT or QUIT signal") ## get traceback info import threading, sys, traceback id2name = dict([(th.ident, th.name) for th in threading.enumerate()]) code =  for threadId, stack in sys._current_frames().items(): code.append("\n# Thread: %s(%d)" % (id2name.get(threadId,""), threadId)) for filename, lineno, name, line in traceback.extract_stack(stack): code.append('File: "%s", line %d, in %s' % (filename, lineno, name)) if line: code.append(" %s" % (line.strip())) worker.log.debug("\n".join(code)) def worker_abort(worker): worker.log.info("worker received SIGABRT signal")
Alright! Now to the fun stuff!
Setup Your Vultr Account
If you haven't already, create a Vultr account. In your Vultr admin, select "Account" in the left-nav and open the "API" section. By default, Vultr API access is disabled. When you enable it, they will provide you with an API key.
Vultr lets you whitelist subnets that can access the Vultr API using the generated key. To use the Nanobox Vultr integration, go ahead and click "Allow All IPv4".
Note: If you're uncomfortable whitelisting all IPv4 addresses, you can whitelist
18.104.22.168/32. This is the subnet on which the Nanobox Vultr adapter is hosted, but this subnet is subject to change. If/when it changes, you will need to manually update your Vultr account with the new subnet.
Add a New Provider to Your Nanobox Account
Select Vultr and click "Proceed."
Nanobox needs your Vultr access token to authenticate with your Vultr account. Paste in your token and click "Verify & Proceed."
Name your provider and choose a default region. The name is arbitrary and only meant to help you identify it in your list of provider accounts.
Launch a New App
Go to the home page of your Nanobox dashboard and click the "Launch New App" button. Select your Vultr provider from the dropdown and choose the region in which you'd like to deploy your app.
Confirm and click "Let's Go!" Nanobox will order a VC2 instance under your Vultr account. When the instance is up, Nanobox will provision platform components necessary for your app to run:
- Load-Balancer: The public endpoint for your application. Routes and load-balances requests to web nodes.
- Monitor: Monitors the health of your server(s) and application components.
- Logger: Streams and stores your app's aggregated log stream.
- Message Bus: Sends app information to the Nanobox dashboard.
- Warehouse: Storage used for deploy packages, backups, etc.
Once all the platform components are provisioned and running, you're ready to deploy your app.
Stage Your App Locally
Nanobox provides "dry-run" functionality that simulates a full production deploy on your local machine. This step is optional, but recommended. If the app deploys successfully in a dry-run environment, it will work when deployed to your live environment.
nanobox deploy dry-run
More information about dry-run environments is available in the Dry-Run documentation.
Add Your New App as a Remote
From the root of your project directory, add your newly created app as a remote.
nanobox remote add app-name
This connects your local codebase to your live app. More information about the
remote command is available in the Nanobox Documentation.
Deploy to Your Live App
With your app added as a remote, you're ready to deploy.
Nanobox will compile and package your application code, send it up to your live app, provision all your app's components inside your live VC2 instance, network everything together, and BOOM! Your app will be live on Vultr.
Manage & Scale
Once your app is deployed, Nanobox makes it easy to manage and scale your production infrastructure. In your Nanobox dashboard you'll find health metrics for all your app's instances/containers. Your application logs are streamed in your dashboard and can be streamed using the Nanobox CLI.
Although every app starts out on a single VC2 instance with containerized components, you can break components out into individual servers and/or scalable clusters through the Nanobox dashboard. Nanobox handles the deep DevOps stuff so you don't have to. Enjoy!
Nanobox Documentation Links
Subscribe to Nanobox
Get the latest posts delivered right to your inbox