├── .gitignore ├── app.ini ├── app ├── __init__.py ├── templates │ └── public │ │ └── index.html └── views.py ├── config.py ├── dev.ini ├── readme.md ├── requirements.txt └── run.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | .hypothesis/ 50 | .pytest_cache/ 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | local_settings.py 59 | db.sqlite3 60 | 61 | # Flask stuff: 62 | instance/ 63 | .webassets-cache 64 | 65 | # Scrapy stuff: 66 | .scrapy 67 | 68 | # Sphinx documentation 69 | docs/_build/ 70 | 71 | # PyBuilder 72 | target/ 73 | 74 | # Jupyter Notebook 75 | .ipynb_checkpoints 76 | 77 | # IPython 78 | profile_default/ 79 | ipython_config.py 80 | 81 | # pyenv 82 | .python-version 83 | 84 | # celery beat schedule file 85 | celerybeat-schedule 86 | 87 | # SageMath parsed files 88 | *.sage.py 89 | 90 | # Environments 91 | .env 92 | .venv 93 | env/ 94 | venv/ 95 | ENV/ 96 | env.bak/ 97 | venv.bak/ 98 | 99 | # Spyder project settings 100 | .spyderproject 101 | .spyproject 102 | 103 | # Rope project settings 104 | .ropeproject 105 | 106 | # mkdocs documentation 107 | /site 108 | 109 | # mypy 110 | .mypy_cache/ 111 | .dmypy.json 112 | dmypy.json 113 | 114 | # Pyre type checker 115 | .pyre/ 116 | 117 | .vscode 118 | dump.rdb 119 | guide.md 120 | 121 | # Uncomment the line below if using in production 122 | # You shouldn't keep your config file in version control 123 | # config.py -------------------------------------------------------------------------------- /app.ini: -------------------------------------------------------------------------------- 1 | [uwsgi] 2 | ; Production .ini file 3 | module = run:app 4 | master = true 5 | 6 | ; There is no magic rule for setting the number of processes or threads to use. 7 | ; It is very much application and system dependent so you'll need to experiment. 8 | processes = 2 9 | threads = 2 10 | 11 | socket = app.sock 12 | chmod-socket = 660 13 | vacuum = true 14 | die-on-term = true -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | 3 | 4 | app = Flask(__name__) 5 | 6 | 7 | if app.config["ENV"] == "production": 8 | 9 | app.config.from_object("config.ProductionConfig") 10 | 11 | elif app.config["ENV"] == "development": 12 | 13 | app.config.from_object("config.DevelopmentConfig") 14 | 15 | else: 16 | 17 | app.config.from_object("config.ProductionConfig") 18 | 19 | 20 | from app import views -------------------------------------------------------------------------------- /app/templates/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Hello, world! 13 | 14 | 15 | 16 | 17 |
18 |
19 |
20 |
SIMPLE FLASK DEMO
21 |

Try sending a query string in the URL

22 | 23 | {% if args %} 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | {% for k, v in args.items() %} 34 | 35 | 36 | 37 | 38 | 39 | {% endfor %} 40 | 41 |
#KeyValue
{{ loop.index }}{{ k }}{{ v }}
42 | {% endif %} 43 | 44 |
45 |
46 |
47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /app/views.py: -------------------------------------------------------------------------------- 1 | from app import app 2 | 3 | from flask import request, render_template 4 | 5 | 6 | @app.route("/") 7 | def index(): 8 | 9 | """ 10 | This route will render a template. 11 | If a query string comes into the URL, it will return a parsed 12 | dictionary of the query string keys & values, using request.args 13 | """ 14 | 15 | args = None 16 | 17 | if request.args: 18 | 19 | args = request.args 20 | 21 | return render_template("public/index.html", args=args) 22 | 23 | return render_template("public/index.html", args=args) -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | class Config: 2 | """ 3 | Use this class to share any default attributes with any subsequent 4 | classes that inherit from Config. 5 | """ 6 | DEBUG = False 7 | TESTING = False 8 | 9 | # Only required when using the session object 10 | # Generated with secrets.token_urlsafe(16) 11 | # You could also use os.urandom(16) 12 | SECRET_KEY = "11Fnw8U6DXrMFvbH9jCdZQ" 13 | 14 | 15 | class ProductionConfig(Config): 16 | """ 17 | This class will inherit any attributes from the parent Config class. 18 | Use this class to define production configuration atrributes, such 19 | as database usernames, passwords, server specific files & directories etc. 20 | """ 21 | pass 22 | 23 | 24 | class DevelopmentConfig(Config): 25 | """ 26 | This class will inherit any attributes from the parent Config class. 27 | Use this class to define development configuration atrributes, such 28 | as local database usernames, passwords, local specific files & directories etc. 29 | """ 30 | DEBUG = True -------------------------------------------------------------------------------- /dev.ini: -------------------------------------------------------------------------------- 1 | [uwsgi] 2 | ; Use this file to run the application with uwsgi locally using 3 | 4 | ; Where run.py is the python file containing the callable app object 5 | module = run:app 6 | 7 | ; This could optionally be achieved with 8 | ; wsgi-file = run.py 9 | ; callable = app 10 | 11 | ; For local development only. The app will be available at localhost:9090 12 | http = :9090 13 | 14 | ; A master process will respawn processes when they die 15 | master = true 16 | 17 | ; By default uWSGI starts with a single process and a single thread 18 | ; We'll start with 4 processes 19 | processes = 4 20 | 21 | ; Each process will have 2 threads 22 | threads = 2 23 | 24 | ; Kill uwsgi with -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ### Introduction 2 | 3 | In this guide, we're going to be building a very simple Flask application that accepts a query string in the URL, renders a template and displays the query string keys & values in a table. 4 | 5 | We're going to be deploying the app on a Google Cloud virtual machine using Ubuntu 18.04. 6 | 7 | You can find the small amount of source code at the Github repo Here if you'd like to follow along. 8 | 9 | The puspose of this guide is to cover the setup of a VM and a basic introduction to deploying a Flask application with Nginx & uwsgi. 10 | 11 | A few things to note about this guide: 12 | 13 | - We won't be using a domain name 14 | - We won't be creating certificates/serving HTTPS requests 15 | - We'll be using Github as a remote repository 16 | 17 | ### How to follow this guide 18 | 19 | There's a few ways to follow along: 20 | 21 | - Clone this repo to your local machine and set up a new remote repository 22 | - Copy the source and create the files/directories yourself on your local machine 23 | 24 | Either way, you'll need to push your code to a remote repo as we'll be pulling the code into the virtual macine. 25 | 26 | ### Dependencies 27 | 28 | - Flask 29 | - uwsgi 30 | 31 | ### Running the application locally 32 | 33 | - Create a new virtual environment with `python -m venv ` 34 | - Clone this repo or create the project files individually 35 | - Activate the virtual environment 36 | - Install the dependencies with `pip install -r requirments.txt` or with `pip install flask uwsgi` 37 | 38 | If you're copying the source code and creating the files/directories yourself, be sure to generate a `requirements.txt` file by running the following command from the app parent directory: 39 | 40 | ```sh 41 | pip freeze > requirements.txt 42 | ``` 43 | 44 | ### Running the application with the Flask development server 45 | 46 | In this example, the entrypoint to our application is `run.py`. 47 | 48 | Enter the following commands in the same directory as `run.py` to run the app with the Flask development server: 49 | 50 | ```sh 51 | export FLASK_APP=run.py 52 | export FLASK_ENV=development 53 | flask run 54 | ``` 55 | 56 | Access the app in your browser at `127.0.0.1:5000`. 57 | 58 | ### Running the appliaction with uwsgi locally 59 | 60 | To run the application locally with `uwsgi`, run the `uwsgi` command followed by the name of the development `ini` file: 61 | 62 | ```sh 63 | uwsgi dev.ini 64 | ``` 65 | 66 | Access the app in your browser at `127.0.0.1:9090`. 67 | 68 | ### Server setup 69 | 70 | In this example, we'll be deploying our application to a virtual machine on Google cloud platform. 71 | 72 | Create a Google cloud account and/or sign into the console. 73 | 74 | ### Creating a free VM 75 | 76 | - Create a new project 77 | - Click the menu icon in the top left of the console 78 | - Select Compute engine > VM instances 79 | - Wait for Compute engine to get ready 80 | 81 | - Click create 82 | - Name the instance 83 | - In `Machine type`, select micro (1 shared vCPU) - It's free! 84 | - In `Boot disk`, select Ubuntu 18.04 LTS and click select 85 | - In the `Firewall` section, tick both Allow HTTP traffic and Allow HTTPS traffic 86 | - Leave everything as is and click create 87 | - Wait for the instance to become ready 88 | 89 | You'll see your External IP address, make a note of it for later! 90 | 91 | ### Adding a network tag 92 | 93 | Now that the instance is ready, we need to add a network tag to enable us to test the application using `uwsgi` (Optional) 94 | 95 | - Click on the VM instance 96 | - Click `EDIT` at the top of the page 97 | - In the `Network tags` section, add `flask` (You may need to add a comma after it) 98 | - Scroll down and hit `Save` 99 | 100 | ### Adding new a firewall rule 101 | 102 | We're going to use port 9090 to test the application with `uwsgi`. But first, we need to add a new firewall rule. (Optional) 103 | 104 | - Select the menu in the Google cloud console 105 | - Click VPC network > Firewall rules 106 | - Click `Create firewall rule` at the top of the page 107 | - Name it `uwsgi-testing` 108 | - Give it a description of `uwsgi testing on port 9090` 109 | - In `Targets`, select `Specified target tags` 110 | - In `Target tags`, enter `flask` 111 | - In `Source filter`, select `IP Ranges` 112 | - In `Source IP ranges`, enter `0.0.0.0/0` 113 | - In `Protocols and ports`, select `Specified protocols and ports` 114 | - Select `TCP` and enter `9090` 115 | - Click `Create` 116 | 117 | To make sure the new firewall rule has been applied: 118 | 119 | - Navigate back to the VM instance `Menu/Compute engine/VM instances` 120 | - Click on the menu icon next to your VM instance and select `View network details` 121 | 122 | In the firewall rules and routes details section, you should see `uwsgi-testing`. 123 | 124 | We're going to come back and disable this rule after we've tested the application with uwsgi! 125 | 126 | ### Connecting to the VM 127 | 128 | We're going to use the Google cloud shell provided to connect to our VM. 129 | 130 | - Click the `SSH` button under the `Connect` section to launch a terminal 131 | - A new shell should be spawned with your username@instance in the prompt 132 | 133 | ### Update the machine 134 | 135 | Update the system packages: 136 | 137 | ```sh 138 | sudo apt update -y;sudo apt upgrade -y 139 | ``` 140 | 141 | ### Installing Python3.7 142 | 143 | We're going to use Python3.7.2 and use `pyenv` to manage our Python installations. 144 | 145 | Clone the `pyenv` repo (It will clone into your user home directory by default): 146 | 147 | ```sh 148 | git clone https://github.com/pyenv/pyenv.git ~/.pyenv 149 | ``` 150 | 151 | For `pyenv` to work, you'll need to add a few lines to your `.bashrc` file. 152 | 153 | Run the following commands to update your `.bashrc` and reload the shell: 154 | 155 | ```sh 156 | echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc 157 | echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc 158 | echo -e 'if command -v pyenv 1>/dev/null 2>&1; then\n eval "$(pyenv init -)"\nfi' >> ~/.bashrc 159 | exec "$SHELL" 160 | ``` 161 | 162 | Install the required Python build dependencies: 163 | 164 | ```sh 165 | sudo apt-get update; sudo apt-get install -y make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev 166 | ``` 167 | 168 | Once the dependencies have been installed, we can install Python3.7.2: 169 | 170 | ```sh 171 | pyenv install 3.7.2 172 | ``` 173 | 174 | You should see (This may take some time on the micro instance!): 175 | 176 | ```sh 177 | Downloading Python-3.7.2.tar.xz... 178 | -> https://www.python.org/ftp/python/3.7.2/Python-3.7.2.tar.xz 179 | Installing Python-3.7.2... 180 | ``` 181 | 182 | Once installed, run the following command to check Python3.7.2 has been installed: 183 | 184 | ```sh 185 | pyenv versions 186 | ``` 187 | 188 | You should see `3.7.2`. 189 | 190 | Now set the system Python as 3.7.2: 191 | 192 | ```sh 193 | pyenv global 3.7.2 194 | ``` 195 | 196 | Start a python interpreter with the `python` command to double check the right version is being called. You should see: 197 | 198 | ```pycon 199 | Python 3.7.2 (default, Mar 20 2019, 23:12:56) 200 | [GCC 7.3.0] on linux 201 | Type "help", "copyright", "credits" or "license" for more information. 202 | >>> 203 | ``` 204 | 205 | ### Cloning the app 206 | 207 | We'll need a method to get the application code from your local machine to the VM. 208 | 209 | Assuming you've cloned this repo to your local machine and set up a new repote origin for yourself, or just copied the code and setup a new repo, we're going to clone into the app on the virtual machine. 210 | 211 | Move into the home directory: 212 | 213 | ```sh 214 | cd ~/ 215 | ``` 216 | 217 | Clone the repo, being sure to replace the URL with the URL of YOUR repo!: 218 | 219 | ```sh 220 | git clone https://github.com/Julian-Nash/flask-demo.git 221 | ``` 222 | 223 | **IMPORTANT** 224 | 225 | If you've cloned this repo, rename the `simple-flask-demo` parent directory to `app`. Otherwise, just make sure the application parent directory is named `app`. 226 | 227 | If you need to rename `simple-flask-demo` on the virtual machine, you can do so with: 228 | 229 | ```sh 230 | mv simple-flask-demo/ app 231 | ``` 232 | 233 | The file/directory structure should look like this (where the parent `app` directory is located in your user home directory): 234 | 235 | ```sh 236 | app 237 | ├── app 238 | │ ├── __init__.py 239 | │ ├── templates 240 | │ │ └── public 241 | │ │ └── index.html 242 | │ └── views.py 243 | ├── app.ini 244 | ├── config.py 245 | ├── dev.ini 246 | ├── readme.md 247 | ├── requirements.txt 248 | └── run.py 249 | ``` 250 | 251 | ### Installing the dependencies 252 | 253 | We need to create a new virtual environment and install the required packages. 254 | 255 | Move into the parent `app` directory: 256 | 257 | ```sh 258 | cd app 259 | ``` 260 | 261 | Running the `ls` command should return: 262 | 263 | ```sh 264 | app app.ini config.py dev.ini readme.md requirements.txt run.py 265 | ``` 266 | 267 | Create a new virtual environment. We're going to call ours `env` (You should too!): 268 | 269 | ```sh 270 | python -m venv env 271 | ``` 272 | 273 | Activate it: 274 | 275 | ```sh 276 | source env/bin/activate 277 | ``` 278 | 279 | Upgrade pip: 280 | 281 | ```sh 282 | pip install --upgrade pip 283 | ``` 284 | 285 | Install the Python dependencies (This may take a few minutes on a micro instance): 286 | 287 | ```sh 288 | pip install -r requirements.txt 289 | ``` 290 | 291 | ### Testing 292 | 293 | We can quickly test our application using uwsgi. 294 | 295 | First, we need to add a firewall rule using `ufw`: 296 | 297 | ```sh 298 | sudo ufw allow 9090 299 | ``` 300 | 301 | Now, make sure `ufw` is enabled: 302 | 303 | ```sh 304 | sudo ufw enable 305 | ``` 306 | 307 | Run the following to make sure `ufw` is enabled and port 9090 is exposed: 308 | 309 | ```sh 310 | sudo ufw status 311 | ``` 312 | 313 | You should see: 314 | 315 | ```sh 316 | Status: active 317 | To Action From 318 | -- ------ ---- 319 | 9090 ALLOW Anywhere 320 | 9090 (v6) ALLOW Anywhere (v6) 321 | ``` 322 | 323 | ### Running with uwsgi 324 | 325 | Assuming the following: 326 | 327 | - Your virtual environment is active 328 | - You've created a new firewall rule to allow `9090` in the Google Cloud console 329 | - You've enabled `ufw` and added `9090` as a rule on the VM 330 | 331 | Make sure you're in the same directory as `dev.ini` and run the application with the following: 332 | 333 | ```sh 334 | uwsgi dev.ini 335 | ``` 336 | 337 | You should see some output from uwsgi in the terminal to let you know uwsgi has started and is running. 338 | 339 | open up a new browser window and head to your virtual machines IP address followed by `:9090`, for example: 340 | 341 | ```sh 342 | http://35.237.110.230:9090 343 | ``` 344 | 345 | You should see the application running! Feel free to send a query string in the URL to have it parsed and returned in the table. 346 | 347 | ```sh 348 | http:///?foo=hello&bar=world&flask=awesome 349 | ``` 350 | 351 | We're going to be using Nginx as a reverse proxy to handle HTTP requests, so once you've had some fun with the application, stop uwsgi with `Ctrl + c`. 352 | 353 | ### Disable the development port 354 | 355 | We used `ufw` to enable traffic on port `9090` but now we need to delete it: 356 | 357 | ```sh 358 | sudo ufw delete allow 9090 359 | ``` 360 | 361 | Run `sudo ufw status` to confirm the rule has been deleted. You should see: 362 | 363 | ```sh 364 | Status: active 365 | ``` 366 | 367 | ### Disabling the firewall rule in GCP 368 | 369 | We should remove the firewall rule we created for testing in the Google cloud console. 370 | 371 | - Navigate to Menu > VPC networking > Firewall rules 372 | - Click `uwsgi-testing` from the list of rules 373 | - Click `Delete` at the top of the page to remove the rule 374 | 375 | If you head back to your VM instance and select `View network details` from the dropdown menu, you'll see the firewall rule has been removed. 376 | 377 | ### Installing Nginx 378 | 379 | We're going to use Nginx to handle incoming HTTP requests to our application, so we need to install and configure it. 380 | 381 | In stall Nginx with the following command: 382 | 383 | ```sh 384 | sudo apt install nginx 385 | ``` 386 | 387 | ### Adjusting the UFW firewall 388 | 389 | Just like how we enabled port `9090` for testing our app with `uwsgi`, we need to enable a few ports to enable Nginx. 390 | 391 | `ufw` will see it as an available application if it's installed. We can chack this by running: 392 | 393 | ```sh 394 | sudo ufw app list 395 | ``` 396 | 397 | You'll see something similar to this: 398 | 399 | ```sh 400 | Available applications: 401 | Nginx Full 402 | Nginx HTTP 403 | Nginx HTTPS 404 | OpenSSH 405 | ``` 406 | 407 | We're only going to be serving our application over HTTP on port 80, so we need to enable it with the following: 408 | 409 | ```sh 410 | sudo ufw allow 'Nginx HTTP' 411 | ``` 412 | 413 | This will allow HTTP traffic on port `80`, the default HTTP port. 414 | 415 | We can check the rule has been applied with: 416 | 417 | ```sh 418 | sudo ufw status 419 | ``` 420 | 421 | You should see: 422 | 423 | ```sh 424 | Status: active 425 | To Action From 426 | -- ------ ---- 427 | Nginx HTTP ALLOW Anywhere 428 | Nginx HTTP (v6) ALLOW Anywhere (v6) 429 | ``` 430 | 431 | ### Checking Nginx 432 | 433 | We can see Nginx is running by heading the the IP address of the virtual machine in a new browser window. 434 | 435 | ```sh 436 | http:// 437 | ``` 438 | 439 | You should be greeted with a default `Welcome to nginx!` page. 440 | 441 | We can also check with `systemd` that Nginx is running. 442 | 443 | `systemd` is a software suite that manages services and processes and will start Nginx when your server boots: 444 | 445 | ```sh 446 | systemctl status nginx 447 | ``` 448 | 449 | You should see: 450 | 451 | ```sh 452 | ● nginx.service - A high performance web server and a reverse proxy server 453 | Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled) 454 | Active: active (running) since Thu 2019-03-21 12:09:59 UTC; 6min ago 455 | Docs: man:nginx(8) 456 | Process: 5761 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (code=exited, status=0/SUCCESS) 457 | Process: 5749 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=0/SUCCESS) 458 | Main PID: 5764 (nginx) 459 | Tasks: 2 (limit: 667) 460 | CGroup: /system.slice/nginx.service 461 | ├─5764 nginx: master process /usr/sbin/nginx -g daemon on; master_process on; 462 | └─5767 nginx: worker process 463 | ``` 464 | 465 | There's still a few more steps before we can access our application: 466 | 467 | - Configure Nginx to reverse proxy requests to uwsgi 468 | - Create a `systemd` unit file to automatically start `uwsgi` and serve our app 469 | 470 | We'll start by creating the `systemd` unit file. 471 | 472 | ### Systemd unit file 473 | 474 | We're going to use `nano` to create a `.service` unit file. We'll call ours `app.service`. 475 | 476 | **IMPORTANT** 477 | 478 | This guide assumes you've used the same directory and file names that we've used throughout this tutorial. 479 | 480 | You'll also need to replace `` with your actual username! You can see your username in your terminal prompt I.e `your-username@your-instance` 481 | 482 | Open `nano` with the following: 483 | 484 | ```sh 485 | sudo nano /etc/systemd/system/app.service 486 | ``` 487 | 488 | Add the following: 489 | 490 | ```sh 491 | [Unit] 492 | Description=A simple Flask uWSGI application 493 | After=network.target 494 | 495 | [Service] 496 | User= 497 | Group=www-data 498 | WorkingDirectory=/home//app 499 | Environment="PATH=/home//app/env/bin" 500 | ExecStart=/home//app/env/bin/uwsgi --ini app.ini 501 | 502 | [Install] 503 | WantedBy=multi-user.target 504 | ``` 505 | 506 | Save and close the file with `Ctrl + c`, followed by `y` then `Enter`. 507 | 508 | Start the process: 509 | 510 | ```sh 511 | sudo systemctl start app 512 | ``` 513 | 514 | Enable the process: 515 | 516 | ```sh 517 | sudo systemctl enable app 518 | ``` 519 | 520 | Check the process status: 521 | 522 | ```sh 523 | sudo systemctl status app 524 | ``` 525 | 526 | You should see: 527 | 528 | ```sh 529 | ● app.service - A simple Flask uWSGI application 530 | Loaded: loaded (/etc/systemd/system/app.service; enabled; vendor preset: enabled) 531 | Active: active (running) since Thu 2019-03-21 14:48:12 UTC; 9min ago 532 | Main PID: 7580 (uwsgi) 533 | Tasks: 5 (limit: 667) 534 | CGroup: /system.slice/app.service 535 | ├─7580 /home/julianjamesnash/app/env/bin/uwsgi --ini app.ini 536 | ├─7592 /home/julianjamesnash/app/env/bin/uwsgi --ini app.ini 537 | └─7595 /home/julianjamesnash/app/env/bin/uwsgi --ini app.ini 538 | ``` 539 | 540 | Now we can move on to the final step, configuring Nginx! 541 | 542 | ### Configuring Nginx 543 | 544 | We need to create a new server block in Nginx's `sites-available`. We'll use `nano` again to create a new file called `app`: 545 | 546 | ```sh 547 | sudo nano /etc/nginx/sites-available/app 548 | ``` 549 | 550 | **IMPORTANT** 551 | 552 | Just as with the `systemd` unit file, you'll need to replace `` with your username and `` with the IP address of your virtual machine! 553 | 554 | Add the following: 555 | 556 | ```sh 557 | server { 558 | listen 80; 559 | server_name ; 560 | 561 | location / { 562 | include uwsgi_params; 563 | uwsgi_pass unix:/home//app/app.sock; 564 | } 565 | } 566 | ``` 567 | 568 | If you'd like to use your own domain name, replace `` with: 569 | 570 | ```sh 571 | server_name example.com www.example.com; 572 | ``` 573 | 574 | You'll need update your domain registrar to point the domain to the server IP address if you want to use a custom domain, which we're not going to cover in this guide. 575 | 576 | We need to link the server block we've just created in `sites-available` to `sites-enabled`: 577 | 578 | ```sh 579 | sudo ln -s /etc/nginx/sites-available/app /etc/nginx/sites-enabled 580 | ``` 581 | 582 | We can check Nginx for syntax errors with the following: 583 | 584 | ```sh 585 | sudo nginx -t 586 | ``` 587 | 588 | You should see: 589 | 590 | ```sh 591 | nginx: the configuration file /etc/nginx/nginx.conf syntax is ok 592 | nginx: configuration file /etc/nginx/nginx.conf test is successful 593 | ``` 594 | 595 | We can now restart the Nginx service: 596 | 597 | ```sh 598 | sudo systemctl restart nginx 599 | ``` 600 | 601 | ### Testing the application 602 | 603 | Assuming you've not had any syntax errors or used different directory/filenames. Go to your IP address in a browser and you should see the application in action. 604 | 605 | Try sending a query string in the URL such as: 606 | 607 | ```sh 608 | /?foo=hello&bar=world&flask=awesome 609 | ``` 610 | 611 | You should see the query string arguments displayed in the table! 612 | 613 | ### Updating your app 614 | 615 | In this scenario, the best way to make changes to your application: 616 | 617 | - Make changes and test locally 618 | - Push the changes to your remote Github repo 619 | - Pull the changes from your virtual machine 620 | 621 | To pull any changes to you've made to your application, make sure you're in the `app` parent directory: 622 | 623 | ```sh 624 | cd ~/app 625 | ``` 626 | 627 | Pull the repo with the following command: 628 | 629 | ```sh 630 | git pull 631 | ``` 632 | 633 | Every time you pull any changes from your remote repo, you'll need to restart the `app` service with: 634 | 635 | ```sh 636 | sudo systemctl restart app 637 | ``` 638 | 639 | If you make any changes to the Nginx `sites-enabled` file, you'll need to restart Nginx with: 640 | 641 | ```sh 642 | sudo systemctl restart nginx 643 | ``` 644 | 645 | ### Wrapping up 646 | 647 | This was just a quick guide to deploying a Flask app to a virtual machine using Nginx & uWSGI and as you can see, it's relitively simple. 648 | 649 | We used Google Cloud but of course, you could achieve the same result using any other provider such as AWS, Linode, Digital Ocean etc. If you do decide to use another cloud platform, you may not have to bother configuring custom firewall rules, it's really platform dependent. 650 | 651 | By modern standards, deploying an application this way may seem slow, especially when compared to using Docker or a hosted service like Google App Engine, however I hope it demonstrates that it's really not that difficult! -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Click==7.0 2 | Flask==1.0.2 3 | itsdangerous==1.1.0 4 | Jinja2==2.10 5 | MarkupSafe==1.1.1 6 | uWSGI==2.0.18 7 | Werkzeug==0.15.0 8 | -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | from app import app 2 | 3 | if __name__ == "__main__": 4 | app.run() --------------------------------------------------------------------------------