New greener region discount. Save 3% on Upsun resource usage. Learn how.
LoginFree trial
FeaturesPricingBlogAbout us
Blog

Up(sun) and running with Flask

FlaskGitGitOpsCLIconfiguration
02 April 2024
Paul Gilzow
Paul Gilzow
Developer Relations Engineer

This guide provides instructions for deploying and working with Flask—a lightweight and popular web framework for building web applications using Python—on Upsun. It is often referred to as a micro framework because it provides the essential components for building web applications but leaves many decisions and extensions up to the developer—let’s dive into those within the context of Upsun. 

If you're more of a just-give-me-the-steps type of person, you can jump straight to the step-by-step included at the end of this guide.

Setting up the application and repository

For the purpose of this guide, we'll start by generating a Flask package project from Cookiecutter, and from there we'll walk through the steps needed to deploy the project on Upsun. With all things in tech, there are many ways to accomplish the same goal; the correct way will depend on your specific needs and goals, and the makeup of your project. The following guide is simply one way to accomplish deploying a Flask application on Upsun, but a way that’s been tried and tested by our team. 

From a terminal/command line prompt, first install Cookiecutter:

> pip3 install cookiecutter

Next, you need to generate the Flask template from Cookiecutter—if this is your first time generating a Flask template, you will need to point to the full GitHub repository address:

> cookiecutter https://github.com/cookiecutter-flask/cookiecutter-flask.git

Otherwise, you can just indicate the specific template you want to generate:
 

> cookiecutter cookiecutter-flask

Cookiecutter will next ask you a series of 10 questions, as you can see below:

[1/10] full_name (): Paul Gilzow
[2/10] email (): paul.gilzow@upsun.com
[3/10] github_username (): gilzow
[4/10] project_name (): my_flask_project
[5/10] app_name (): my_flask_cookie
[6/10] project_short_description (): A demonstration project
[7/10] use_pipenv (): n
[8/10] python_version (): 3.11
[9/10] node_version (): 18
[10/10] use_heroku (): N

Answer each question, paying attention to what you use for the app_name question as you will need it later. Once Cookiecutter has generated the template, cd into the directory it just created; it will be the same name you gave for the app_name question. For this example, I named it my_flask_cookie and will refer to it throughout the remainder of the guide. 
You need to initiate the contents of this directory as a Git repository so before doing anything else, initialize the repository:

> git init .

By default, Git will still use master as the name for the initial branch. If you wish to change the default branch name, you can do so with the git branch -m command. I'll rename mine to main:

> git branch -m main

 

Preparing Flask for Upsun deployment

Now that we have the repository initialized, and our template generated, we're ready to prepare it for use on Upsun. Before attempting the next command, make sure you have the Upsun CLI tool installed and working, and that you have authenticated the CLI tool with your Upsun account. Once those tasks are complete, you're now ready to have the Upsun CLI tool generate the configuration files we'll need to deploy on Upsun.

> upsun project:init

Please note: this command is also available as upsun ify

The Upsun CLI tool will now ask you a series of questions to determine your project's requirements as you can see below: 

> upsun project:init
Welcome to Upsun!
Let's get started with a few questions.

We need to know a bit more about your project. This will only take a minute!

What language is your project using? We support the following:
Use arrows to move up and down, type to filter
  C#/.Net Core
  Elixir
  Go
  Java
  Lisp
  JavaScript/Node.js
  PHP
> Python
  Ruby

Scroll down and select Python, it should then automatically detect your dependency manager: 

What language is your project using? We support the following: [Python]

✓ Detected dependency managers: Pip

It will then ask for the name of your application and from there it should prompt you for the services your project needs. Select each one and then hit enter. For this example, I only need PostgreSQL:

                                               (\_/)
We're almost done...  =(^.^)=

Last but not least, unless you're creating a static website, your project uses services. Let's define them:

Select all the services you are using:
Use arrows to move, space to select, type to filter
  [ ]  MariaDB
  [ ]  MySQL
> [x]  PostgreSQL
  [ ]  Redis
  [ ]  Redis Persistent
  [ ]  Memcached
  [ ]  OpenSearch

It will then generate a series of configuration files for you: 
 

┌───────────────────────────────────────────────────┐
│   CONGRATULATIONS!                                                                                                                      │
│                                                                                                                                                                     │
│   We have created the following files for your:                                                                         │
│     - .environment                                                                                                                                  │
│     - .upsun/config.yaml                                                                                                                       │
│                                                                                                                                                                     │
│   We're jumping for joy! ⍢                                                                                                                  │
└───────────────────────────────────────────────────┘
        │ /
        │/
        │
  (\ /)
  ( . .)
  o (_(")(")

You can now deploy your application to Upsun!
To do so, commit your files and deploy your application using the Upsun CLI:
  $ git add .
  $ git commit -m 'Add Upsun configuration files'
  $ upsun project:set-remote
  $ upsun push

Lastly, you need to add all of your generated files, from both Cookiecutter and the Upsun CLI tool to your Git repository:

> git add .
> git commit -m "initial commit"

Before you can deploy your application, you'll need to create a new project on Upsun from the command line:

> upsun project:create

The CLI tool will now walk you through the creation of a project asking you for your organization, the project's title, the region you want the application housed, and the branch name (use the same one you set earlier). For now, allow the CLI tool to set Upsun as your repository's remote, and then select Y to allow the tool to create the project. The Upsun bot will begin the generation of your Upsun project and once done, will report back the details of your project including the project's ID, and URL where you can manage the project from the Upsun web console. Don't worry if you forget any of this information, you can retrieve it later with:

> upsun project:info

And you can launch the web console for your project at any time by doing the following:
 

> upsun web

Now that we have our Upsun project created and our local project generated and associated with the Upsun project, the only thing left to do is add configurations that are specific to the application. To start, we need to add an environment variable for FLASK_APP for all environments that points to our autoapp.py file. Open the file config.yaml located in the .upsun directory that the CLI tool generated and locate the commented line that starts with # Variables to control the environment.. We need to uncomment the next two lines underneath this line, and add our environmental variable to the list:
 

# Variables to control the environment. More information: https://docs.upsun.com/create-apps/app-reference.html#variables
variables:
env:
  FLASK_APP: autoapp.py
#     # Add environment variables here that are static.
#     PYTHONUNBUFFERED: "1"

 

Please note: when uncommenting a section, make sure you remove both the comment marker # as well as the extra space. If you don't remove the extra space, you will end up with an Invalid block mapping key indent error when the configuration file is validated.

Static assets

Next, you're going to need some writable disk space to hold the static assets that npm builds and flask-static-digest generates. This directory exists under our application package named, `./<application-name>/static`. In ./.upsun/config.yml, find the line that starts with # Mounts define directories that are writable. You'll need to uncomment the line # mounts: and then add an entry describing where we want a writable mount added:
 

# Mounts define directories that are writable after the build is complete.
# More information: https://docs.upsun.com/create-apps/app-reference.html#mounts
mounts:
 "my_flask_cookie/static":
   source: "storage" 
   source_path: "static_assets" 

source indicates the type of mount: storage, tmp, or service and source_path specifies where the mount points inside the external directory. For further information, please see the documentation on mounts

The build hook allows us to make changes to the application before it is finalized and deployed. You should notice that when the CLI tool generated the configuration file for me earlier in the process, it automatically added pip install -r requirements.txt! This same section is where you'll also instruct Upsun to install your npm packages. But before that, I usually like to upgrade pip before I run pip install so I'm going to add a new line above that and add in pip install --upgrade pip. Then I'll add another line after the initial pip install and add npm install:
 

# Hooks allow you to customize your code/environment as the project moves through the build and deploy stages
# More information: https://docs.upsun.com/create-apps/app-reference.html#hooks
hooks:
 # The build hook is run after any build flavor.
 # More information: https://docs.upsun.com/create-apps/hooks/hooks-comparison.html#build-hook
 build: |
   set -eux
   pip install --upgrade pip
   pip install -r requirements.txt
   npm install 

You also need to inform Upsun what should occur when your application is deployed. The deploy hook is similar to the build hook but runs after the application image has been built. At this stage the application image is read-only, but your writable disk space has been mounted and is now accessible. Find the deploy: YAML key, add a new line after set -eux, and add npm run build.

# The deploy hook is run after the app container has been started, but before it has started accepting requests.
  deploy: |
    set -eux
    npm run build

Next, we need to configure how Upsun will handle requests to this application image. In ./.upsun/config.yaml locate the line that starts with # The web key configures the web server running in front of your app. Beneath that line there should be a YAML property of commands:.  A few lines beneath that line will be a YAML property of start:. Once again, the CLI tool already added some information here, but since it doesn't know the specifics of what needs to be used, it simply left instructions for you to replace with your own start command. For now, you only need the basic Flask server, so replace the current contents with flask run -p $PORT

web:
 # Commands are run once after deployment to start the application process.
 # More information: https://docs.upsun.com/create-apps/app-reference.html#web-commands
 commands:
   # The command to launch your app. If it terminates, it's restarted immediately.
   # You can use the $PORT or the $SOCKET environment variable depending on the socket family of your upstream
   start: "flask run -p $PORT"

Since you're using the Flask server (for now), you will also need to change the socket_family from unix to tcp:

start: "flask run -p $PORT"
# You can listen to a UNIX socket (unix) or a TCP port (tcp, default).
# Whether your app should speak to the webserver via TCP or Unix socket. Defaults to tcp
# More information: https://docs.upsun.com/create-apps/app-reference.html#where-to-listen
upstream:
 # Whether your app should speak to the webserver via TCP or Unix socket. Defaults to tcp
 # More information: https://docs.upsun.com/create-apps/app-reference.html#where-to-listen
 socket_family: tcp

We've now added all the configuration Upsun needs to be able to build and deploy your application! Let's go ahead and commit these changes:

> git add ./upsun/config.yaml
> git commit -m "adds FLASK_APP env var, adds mount for static builds, build commands, npm run build on deploy, web start command"

 

Preparing the application for Upsun

While we've finished telling Upsun what it needs to do to build and deploy your application, your application still needs to know some things about Upsun. This type of information typically goes into your .env file. Upsun generates all of this type of data and exposes it to the application as environmental variables in the deployed image. Since the information is dynamic, and changes from environment to environment, you don't want to commit those static values in a .env file. Instead, Upsun supports a .environment file that is sourced in the application image, as well as your shell when you SSH into the container. For a list of all the variables that Upsun generates, please refer to the documentation on the provided environmental variables.


Open the .environment file that the CLI tool generated earlier. Notice it has already created some environmental variables for you to connect to your database service. However, since you will later need to use a tunnel to generate your migration files, you need to replace what the Upsun CLI tool generated, and replace it with the following:

export RELATIONSHIPS_JSON="$(echo $PLATFORM_RELATIONSHIPS | base64 --decode)"

# Set database environment variables
export DB_HOST="$(echo $RELATIONSHIPS_JSON | jq -r '.postgresql[0].host')"
export DB_PORT="$(echo $RELATIONSHIPS_JSON | jq -r '.postgresql[0].port')"
export DB_DATABASE="$(echo $RELATIONSHIPS_JSON | jq -r '.postgresql[0].path')"
export DB_USERNAME="$(echo $RELATIONSHIPS_JSON | jq -r '.postgresql[0].username')"
export DB_PASSWORD="$(echo $RELATIONSHIPS_JSON | jq -r '.postgresql[0].password')"
export DB_CONNECTION="$(echo $RELATIONSHIPS_JSON | jq -r '.postgresql[0].scheme')"
export DATABASE_URL="postgresql://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_DATABASE}"

You'll need to add a few more for our Flask application so Flask has what it needs to be able to function properly. To start, you need to add the remainder of the variables that are defined in .env. You already took care of FLASK_APP in the ./.upsun/config.yaml file so that leaves the following: 

  • FLASK_ENV
  • FLASK_DEBUG
  • LOG_LEVEL
  • SEND_FILE_MAX_AGE_DEFAULT
  • SECRET_KEY
  • GUNICORN_WORKERS

Upsun provides us with information about what type of environment the application is running in via an environmental variable named PLATFORM_ENVIRONMENT_TYPE, the values of which can be production, development, or staging. Inside of the .environment file, add the following line:

export FLASK_ENV="${PLATFORM_ENVIRONMENT_TYPE}"

However, several of the other environmental variables also need to change whether or not you are on a production environment so you can leverage the information in PLATFORM_ENVIRONMENT_TYPE to not only set FLASK_ENV but also several of the other variables. You only want FLASK_DEBUG enabled (1) if you're not running in production. Inside the .environment file, add the following line:

export FLASK_DEBUG=$( [ "${PLATFORM_ENVIRONMENT_TYPE}" = "production" ] && echo 0 || echo 1)

If the environment you're in is production, you'll return 0 (disabled), otherwise you'll return 1 (enabled). Let's do something similar for LOG_LEVEL. Inside the .environment file, add the following line:

export LOG_LEVEL=$( [ "${PLATFORM_ENVIRONMENT_TYPE}" = "production" ] && echo "info" || echo "debug")

If the environment you're in is production, set LOG_LEVEL to "info", otherwise set it to "debug". 

The last environmental variable that needs to be different based on environment type is SEND_FILE_MAX_AGE_DEFAULT where we want it to be 0 if we're not in production, but a higher value if we are. Inside the .environment file, add the following line: 

export SEND_FILE_MAX_AGE_DEFAULT=$( [ "${PLATFORM_ENVIRONMENT_TYPE}" = "production" ] && echo 31556926 || echo 0)

The next environmental variable you need to set is SECRET_KEY. It is used for securely signing the session cookie and can be used for any other security-related needs by extensions or your application. It should be a long random string. Again, Upsun provides us with something for exactly this as an environmental variable: PLATFORM_PROJECT_ENTROPY. Inside the .environment file, add the following line:  

export SECRET_KEY="${PLATFORM_PROJECT_ENTROPY}"

Since for no, we're using Flask as our web server, you canl skip adding GUNICORN_WORKERS to your .environment file. We'll cover switching to gunicorn in a later blog post.

Your .environment file should now look similar to the following:

# Set database environment variables
export DB_HOST="$POSTGRESQL_HOST"
export DB_PORT="$POSTGRESQL_PORT"
export DB_PATH="$POSTGRESQL_PATH"
export DB_USERNAME="$POSTGRESQL_USERNAME"
export DB_PASSWORD="$POSTGRESQL_PASSWORD"
export DB_SCHEME="postgresql"
export DATABASE_URL="${DB_SCHEME}://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_PATH}"

export FLASK_ENV="${PLATFORM_ENVIRONMENT_TYPE}"
export FLASK_DEBUG=$( [ "${PLATFORM_ENVIRONMENT_TYPE}" = "production" ] && echo 0 || echo 1)
export LOG_LEVEL=$( [ "${PLATFORM_ENVIRONMENT_TYPE}" = "production" ] && echo "info" || echo "debug")
export SEND_FILE_MAX_AGE_DEFAULT=$( [ "${PLATFORM_ENVIRONMENT_TYPE}" = "production" ] && echo 31556926 || echo 0)
export SECRET_KEY="${PLATFORM_PROJECT_ENTROPY}"

Since you've made changes to your .environment file, you'll need to commit those changes before pushing the repository to Upsun:

> git add .environment
> git commit -m "adds needed flask environmental variables"

Now you can push the changes to Upsun and activate your initial environment:

> upsun environment:push

Answer Y to the question: "Are you sure you want to push to the main (type: production) branch?

Upsun will now read your configuration files, and begin building your application image. Assuming you have no syntax errors in your configuration files, it should build and deploy your image and at the end of the process, report back the URLs associated with our project. 

Setting up the database

You may have noticed that we haven't done anything in regard to a database. This application uses Flask-migrate and since this is a brand new application, you'll need to set up the initial migrations, and commit them so you can then have them applied to our Upsun database. However, because the migrate command needs access to the database, you'll need to set up a temporary local environment and give it a way to access the database service. Let's first set up a virtual environment to run your project inside of:

> python3 -m venv env && source venv/bin/activate

Just like in your build hook, update pip and install your requirements:

> pip install --upgrade pip
> pip install -r requirements.txt

Next, you're going to set up this local instance so it can communicate with your database service. When you pushed to Upsun previously, it created and deployed your database service. The Upsun CLI tool gives you a method to communicate with your application's services: upsun tunnel

> upsun tunnel:open -y

This opens an SSH tunnel to all the services for the application, and you can now use it to allow your local instance to communicate with them as if they were local too. To do that though, you'll need to configure some environmental variables similarly to how you did previously. If you reopen the .environment file, you'll notice at the top that we make use of an environment variable named $PLATFORM_RELATIONSHIPS to retrieve information about services and their credentials. The tunnel you created gives you access to that same data, allowing you to generate a local PLATFORM_RELATIONSHIPS environment variable containing the same information.

> export PLATFORM_RELATIONSHIPS="$(upsun tunnel:info --encode)"

If you now try echo $PLATFORM_RELATIONSHIPS you'll see it has been set to a fairly large base64 encoded value. This string contains your services, their definitions, locations, and most importantly, their credentials. Because you have this environmental variable set locally, you can reuse your .environment file for Upsun to recreate many of the other environmental variables you need to run locally. 

Although, you will have a few that aren't set via PLATFORM_RELATIONSHIPS that you still need to be set up.

> export PLATFORM_ENVIRONMENT_TYPE=production
> export PORT=8888
> export PLATFORM_PROJECT_ENTROPY=$(openssl rand -base64 32)

Lastly, source your .environment file to finish setting up all the environmental variables in your current shell:

> source ./.environment

You now have everything you need for Flask-Migrate to be able to connect to the database and generate your migration files. First, you need to have Flask-Migrate initiate the migrations directory and prepare for the migrate command:

> flask db init 

Now you can have Flask-migrate generate your migrations:

> flask db migrate

And then commit your generated migrations:

> git add migrations/*
> git commit -m "adds migrations"

You now need to instruct Upsun to run the Flask-Migrate upgrade command when deploying so you know any migration changes are automatically applied. Re-open the ./.upsun/config.yaml and find the deploy hook where you added npm run build. On the next line, add flask db upgrade

# The deploy hook is run after the app container has been started, but before it has started accepting requests.
# More information:    https://docs.upsun.com/create-apps/hooks/hooks-comparison.html#deploy-hook
      deploy: |
        set -eux
        npm run build
        flask db upgrade

Commit the changes:

> git add ./.upsun/config.yaml
> git commit -m "adds flask db upgrade to deploy hook"

And finally, push everything up to your Upsun environment!

> upsun environment:push -y

Congrats! You've now successfully deployed your Flask application to Upsun, take a moment to visit your site and test it out. 

In future posts, we'll explore the different options you have for web servers, a more robust local development environment, adding source control integration, and adding various services to your project. But for now, go forth and deploy (even on Fridays)!

A quick step-by-step

For those of you who prefer to just get started as soon as possible, here is a quick and simple step-by-step guide: 

`T:` - run the line in a terminal/command prompt

  1. T: pip3 install cookiecutter
  2. T: cookiecutter https://github.com/cookiecutter-flask/cookiecutter-flask.git
  3. Answer questions:
    1. Name
    2. Email
    3. Github_username
    4. Project name
    5. App name
    6. Description
    7. Pipenv
    8. Python version
    9. Node version
    10. heroku
  4. Cd into the directory (should be what you answered for 3e)
  5. T: git init .
  6. T: git branch -m main
  7. T: upsun project:init
    1. Select Python
    2. Select Postgres 
  8. T: git add .
  9. T: git commit -m "init commit"
  10. T: upsun p:create
  11. Answer questions
    1. Choose your org
    2. Give it a title
    3. Choose a region
    4. Enter the branch name from #7
    5. Set the project as the remote (for now)
    6. Select Y to "continue"
  12. Open ./.upsun/config.yaml
    1. Find the section describing "Variables"
    2. Uncomment "# variables" and the next line "env:"
    3. On the next line, add FLASK_APP: autoapp.py
    4. Find the section describing "mounts"
    5. Uncomment "# mounts:"
    6. On the next line add 
        "<name-of-your-app-from-3e-above>/static":
          source: storage
          source_path: static_build
    7. Find the section for hooks:build
    8. On the line before pip install, add the following:
      pip install --upgrade pip
    9. On the line below pip install, add the following
      npm install
    10. Scroll down to the deploy section
    11. On the line after set -eux, add
      npm run build
    12. Find the section web:commands:start and change it to 
      start: flask run -p $PORT
    13. Below this find the section for upstream:socket_family and either comment out both lines, or change "unix" to "tcp"
  13. T: git add ./.upsun/config.yaml
  14. T: git commit -m "adds FLASK_APP env var, adds mount for static builds, build commands, npm run build on deploy, web start command"
  15. Open .environment file
    1. Remove everything and replace with:
      # Set database environment variables
      export RELATIONSHIPS_JSON="$(echo $PLATFORM_RELATIONSHIPS | base64 --decode)"
      # Set database environment variables
      export DB_HOST="$(echo $RELATIONSHIPS_JSON | jq -r '.postgresql[0].host')"
      export DB_PORT="$(echo $RELATIONSHIPS_JSON | jq -r '.postgresql[0].port')"
      export DB_DATABASE="$(echo $RELATIONSHIPS_JSON | jq -r '.postgresql[0].path')"
      export DB_USERNAME="$(echo $RELATIONSHIPS_JSON | jq -r '.postgresql[0].username')"
      export DB_PASSWORD="$(echo $RELATIONSHIPS_JSON | jq -r '.postgresql[0].password')"
      export DB_CONNECTION="$(echo $RELATIONSHIPS_JSON | jq -r '.postgresql[0].scheme')"
      export DATABASE_URL="postgresql://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_DATABASE}"
    2. Add the following lines to .environment:
      export SECRET_KEY="${upsun_PROJECT_ENTROPY}"
      export FLASK_DEBUG=$( [ "${upsun_ENVIRONMENT_TYPE}" = "production" ] && echo 0 || echo 1)
      export FLASK_ENV="${upsun_ENVIRONMENT_TYPE}"
      export GUNICORN_WORKERS=1
      export LOG_LEVEL=$( [ "${upsun_ENVIRONMENT_TYPE}" = "production" ] && echo "info" || echo "debug")
      # In production, set to a higher number, like 31556926
      export SEND_FILE_MAX_AGE_DEFAULT=$( [ "${upsun_ENVIRONMENT_TYPE}" = "production" ] && echo 31556926 || echo 0)
  16. T: git add .environment
  17. T: git commit -m "adds needed flask env vars" 
  18. T: upsun e:push
    1. Answer Y
  19. At the end of the deploy, you'll be given your project's URL
  20. T: python3 -m venv env
  21. T: source venv/bin/activate
  22. T: pip install --upgrade pip
  23. T: pip install -r requirements.txt
  24. T: upsun tunnel:open -y
  25. T: export upsun_RELATIONSHIPS="$(upsun tunnel:info --encode)"
  26. T: export PORT=8888
  27. T: export upsun_PROJECT_ENTROPY=$(openssl rand -base64 32) 
  28. T: export upsun_ENVIRONMENT_TYPE=production
  29. T: source ./.environment
  30. T: flask db init
  31. T: flask db migrate
  32. Open ./.upsun/config.yaml
    1. Find the section for hooks:deploy
    2. On a new line after `npm run build`, add `flask db upgrade`
  33. T: git add migrations/*
  34. T: git commit -m "adds migrations"
  35. T: git add ./.upsun/config.yaml
  36. T: git commit -m "adds flask db upgrade to deploy hook"
  37. T: upsun environment:push
Upsun Logo
Join the community