How to migrate (or create) and deploy a Flask project¶
This guide will take you through the steps to deploy a portable, vendor-neutral Flask project, either by building it from scratch or migrating an existing application, using Docker. The project architecture is in line with Twelve-factor design principles.
This guide assumes that you are familiar with the basics of the Divio platform and have Docker and the Divio CLI installed.
Edit (or create) the project files¶
Start in an existing Flask project, or if necessary, create a new directory.
Create a minimal application if required¶
You may already have a Flask application of your own to migrate, but if not, the example below provides a minimal one.
The example is taken from the Flask tutorial’s own flaskr example. Create a
directory, containing an
import os from flask import Flask def create_app(test_config=None): # create and configure the app app = Flask(__name__, instance_relative_config=True) app.config.from_mapping( SECRET_KEY = os.environ.get('SECRET_KEY', 'dev'), DATABASE = os.environ.get('DATABASE_URL', os.path.join(app.instance_path, 'flaskr.sqlite'), STORAGE = os.environ.get('DEFAULT_STORAGE_DSN') ) if test_config is None: # load the instance config, if it exists, when not testing app.config.from_pyfile('config.py', silent=True) else: # load the test config if passed in app.config.from_mapping(test_config) # ensure the instance folder exists try: os.makedirs(app.instance_path) except OSError: pass # a simple page that says hello @app.route('/hello') def hello(): return 'Hello, World!' return app
Note the highlighted sections above, in which the application obtains configuration values from its environment. If you are working on your own application that has database or other configuration of this kind, you should adapt it so that it is similarly able to obtain these values.
The next step is to Dockerise the application.
Create a file named
FROM python:3.8 WORKDIR /app COPY . /app RUN pip install -r requirements.txt # Select one of the following application gateway server commands CMD uwsgi --http=0.0.0.0:80 --module="flaskr:create_app()" CMD gunicorn --bind=0.0.0.0:80 --forwarded-allow-ips="*" "flaskr:create_app()"
You may need to change the version of Python, and should also select the
CMD that will start your preferred gateway
server for production - if you’re not using the
flaskr example, you’ll need to amend the name in the command too.
Python requirements in
Dockerfile expects to find a
requirements.txt file, so add one if required. Where indicated below, choose
the appropriate options to install the components for Postgres/MySQL if you plan to use them, and uWSGI/Gunicorn, for
flask==1.1.2 # Select one of the following for the database as required psycopg2==2.8.5 mysqlclient==2.0.1 # Select one of the following for the gateway server uwsgi==184.108.40.206 gunicorn==20.0.4
Check that the version of Flask is correct, and include any other Python components required by your project.
Local container orchestration with
docker-compose.yml file, for local development purposes. This will replicate
web image used in cloud deployments, allowing you to run the application in an environment as close to that of
the cloud servers as possible. Amongst other things, it will allow the project to use a Postgres or MySQL database
running in a local container, and provides convenient access to files inside the containerised application.
You will need to include/delete the highlighted sections below appropriately:
version: "2.4" services: web: # the application's web service (container) will use an image based on our Dockerfile build: "." # map the internal port 80 to port 8000 on the host ports: - "8000:80" # map the host directory to app (which allows us to see and edit files inside the container) volumes: - ".:/app:rw" - "./data:/data:rw" # the default command to run whenever the container is launched command: flask run --host=0.0.0.0 --port=80 # the URL 'postgres' or 'mysql' will point to the application's db service links: - "database_default" env_file: .env-local database_default: # Select one of the following db configurations for the database image: postgres:9.6-alpine environment: POSTGRES_DB: "db" POSTGRES_HOST_AUTH_METHOD: "trust" SERVICE_MANAGER: "fsm-postgres" volumes: - ".:/app:rw" image: mysql:5.7 environment: MYSQL_DATABASE: "db" MYSQL_ALLOW_EMPTY_PASSWORD: "yes" SERVICE_MANAGER: "fsm-mysql" volumes: - ".:/app:rw" - "./data/db:/var/lib/mysql" healthcheck: test: "/usr/bin/mysql --user=root -h 127.0.0.1 --execute \"SHOW DATABASES;\"" interval: 2s timeout: 20s retries: 10
Local configuration using
As you will see above, the
web service refers to an
env_file containing the environment variables that will be
used in the local development environment. Create a
.env-local file. As with the
DATABASE_URL as required.
FLASK_APP variable is used by the
flask run command. It assumes that your application can be found at
flaskr; amend this appropriately if required.
# Select one of the following for the database DATABASE_URL=postgres://postgres@database_default:5432/db DATABASE_URL=mysql://root@database_default:3306/db DEFAULT_STORAGE_DSN=file:///data/media/?url=%2Fmedia%2F DOMAIN_ALIASES=localhost, 127.0.0.1 SECURE_SSL_REDIRECT=False FLASK_APP=flaskr FLASK_ENV=development
With this, you have the basics for a Dockerised application that can equally effectively be deployed in a production environment or run locally, using environment variables for configuration in ether case.
Build with Docker¶
Now you can build the application containers locally:
It’s beyond the scope of this guide to cover configuration in detail, as that will depend to a great extent on the application you have or are planning to build. However the basic principle for all configuration is similar: exactly the same application code should run without modification whether locally or in one of the multiple cloud environments, and all configuration should be provided by environment variables.
flaskr example above, the database configuration is read from the
DATABASE_URL environment variable, and
falls back to use SQLite if not provided.
Each Divio cloud environment with a database attached to it will be provided automatically with a
DATABASE_URL environment variable. In the
docker-compose.yml files above, example
configuration is provided so that when running locally, the application can use the same database type as it does in
production. (This is a much more satisfactory approach than using say Postgres in production and SQLite for
If your application needs to handle media, it should parse the
DEFAULT_STORAGE_DSN to configure an appropriate
storage interface. Each Divio cloud environment with media object storage provisioned will be provided with a
.env-local to configure storage for local development. This can be one of the cloud
storage instances, but it’s often convenient to use local file storage rather than a cloud media store (as in the
file:///data/media/?url=%2Fmedia%2F) if your Flask code can handle both kinds of storage backend.
Serving static files¶
Check the local site¶
To start up the site locally to test it:
and access it at http://127.0.0.1:8000/hello (if using the
Test using the production gateway server¶
In cloud environments: the
Dockerfile contains a
CMD that starts up Flask using the uWSGI/Gunicorn or other
application gateway server.
In the local environment: the
command line in
docker-compose.yml starts up Flask using the
command, overriding the
CMD in the
Dockerfile. If the
command line is commented out,
will use the application gateway server locally instead.
Deployment and further development¶
Create a new project on Divio¶
In the Divio Control Panel add a new project, selecting the Build your own option.
Connect the local project to the cloud project¶
Your Divio project has a slug, based on the name you gave it when you created it. Run
divio project list -g to
get your project’s slug; you can also read the slug from the Control Panel.
divio project configure
and provide the slug. (This creates a new file in the project at
If you have done this correctly,
divio project dashboard will open the project in the Control Panel.
Configure the Git repository¶
Initialise the project as a Git repository if it’s not Git-enabled already:
git init .
.gitignore file is needed to exclude unwanted files from the repository. Add:
# Python *.pyc *.pyo db.sqlite3 # Divio .divio /data.tar.gz /data # OS-specific patterns - add your own here .DS_Store .DS_Store? ._* .Spotlight-V100 .Trashes
Add the project’s Git repository as a remote, using the slug value in the remote address:
git remote add origin firstname.lastname@example.org:<slug>.git
divio instead if you already have a remote named
Commit your work¶
git add . # add all the newly-created files git commit -m "Created new project" # commit git push --set-upstream --force origin [or divio] master # push, overwriting any unneeded commits made by the Control Panel at creation time
You’ll now see “1 undeployed commit” listed for the project in the Control Panel.
Deploy the Test server¶
divio project deploy
(or use the Deploy button in the Control Panel).
Once deployed, your project will be accessible via the Test server URL shown in the Control Panel.