Introduction to the Deployment Stack
I'm going to show you how to take your development flask app and deploy it to a production server. And the way we're going to do this is with something called Gunicorn, which is a web server Gateway interface that's going to interface with your flask cap. And on the other side of that you're going to have an Nginx proxy server. So basically a user somewhere on the Internet is going to visit your website via the Nginx proxy server. That's going to talk to Gunicorn, which is going to talk to your flask app, execute the Python code, and send it back down the chain.
Prerequisites and Domain Setup
This is part of a two-part video series. The first part was making that flask app. I'm going to assume that you already went through that tutorial or you have a flask app that you want to deploy. Otherwise, I have code on GitHub which is linked below that you can catch up with us as part of this tutorial.
So otherwise the only change that I made since the previous video is I, while I'm working with Vultr and this is the IP address of my server, so all I did was I went into Google Domains, which is where I registered tonybony.com, and created an A record that maps tonybony.com to the IP address. So before we were accessing our flask app via the IP address directly, and now if I start the server back up, instead of using the IP address, which should still work, let's type in tonybony.com:5000 instead and we will serve the same exact web page. Okay, so let's get on to the tutorial.
Creating a Secure User Account
The first thing I'm going to do, let's get out of here. As you see, we are using the root user, which is not a good idea for production servers, whether that's a Flask app or something else. Let's make a new user and run the app under that new user. Okay, so we're going to do add user Tony, and that's going to add that user. Ask for your password for that user. I'm going to type in a password and I'll confirm it. Give them my name, and I'm just going to leave the rest of this blank. That looks good. And if this user needs pseudo privileges, which we will to install some packages, we can do usermod -aG sudo and then the name of the user. Hit enter, and now that user has sudo privileges.
Configuring SSH and Logging In
To make sure this user can log into the remote server via SSH, let's just double check inside of our /etc/ssh/sshd_config file. And then here there's something called password authentication. Let's set that to yes so that way the user that we just created with this password can log into the server. And to apply those changes we will do a systemctl restart sshd. And now we can exit out of the server as root and log back in with our new user. So ssh Tony@ and this time we don't have to use the IP address we can use the domain name if you're following along, so tonybony.com. Hit enter, type in that user's password, and now we are logged in as the new user to our remote Ubuntu Server.
Setting Up the Project Environment
Now like I was saying, I have some code up here on GitHub at github.com/tonyflow. If you go into my repository section, you can clone the flask app demo that we've been working on up until the previous video. So just come in here, copy that URL, and then open up your terminal window and we'll do git clone that URL and we will clone it into the hike app just like we did before. So that's going to download all that code. Now we have a hike folder and because this is a new user, we have to set up our Python virtual environment again. So let's do that next. Let's do python3 -m venv and then we're going to make that virtual environment inside of an Env folder on our desktop called Teton, T-E-T-O-N.
And when that's finished being created, we can activate the virtual environment: source env/Teton/bin/activate. All right, we are now inside of our virtual environment. From this point forward any Python or pip commands will be self-contained in this environment. So that being said, let's do pip install flask because we will need that. And with that installed, let's go into our hike directory and let's bring it up: python peak.py. And let's just make sure everything's working out. So we will refresh the page over here and yep, there we go, let's go climb a mountain. All right.
Installing and Running Gunicorn
Next, let's set up the web server gateway interface with Gunicorn. So let's Control-C to get out of our development server and we'll do a pip install gunicorn. Hit enter and that installs really quick.
So inside of our hike directory, right next to peak.py, let's make a web_server_gateway_interface.py file. And this is going to be very similar to, let me just show you over here. So as a reminder in peak.py we have from flask import Flask and then if __name__ == '__main__' we do this. We're going to do something very similar in our web server gateway interface. So from peak import app. Okay so let's review, from this in this directory there's a file called peak, and inside peak we define an app variable. Okay, so that's all that is doing. We're importing that app variable here and then if, like we did before, if __name__ == '__main__' we're going to run our app, app.run(), and we're going to run the app without any of those other host names or port numbers. Before we were doing the development server stuff here, but now we are running the app directly, and now after this we're going to set up the infrastructure to handle that in a production environment.
But let's make sure Gunicorn is working. We'll run gunicorn --bind 0.0.0.0:5000 kind of like we were doing before and then we're going to specify this web server gateway interface file that's in this current directory, colon app, which we imported into that file. So let's run that and let's see if our website still loads. Okay, there you go.
Creating a Systemd Service for Gunicorn
So let's go back here, get out of our development server, and deactivate our Python virtual environment. Now what we're going to do is set up the system process system service file that's going to basically point to our Python virtual environment and point to our Flask app so that way if the server ever reboots or the process crashes in some cases it'll automatically bring it back up. And we can access the systemctl start stop restart status commands as I'll show you in just a bit.
So let's go ahead and do that. Let's make a new file in the /etc/systemd/system directory and we'll call it peak.service. Okay, in here rather than typing everything in front of you, I'm going to just copy and paste some code. This code is also available on the GitHub, and I'll just walk through it line by line to show you kind of what's going on. So this is just a description of the service that we are creating, that's the unit Gunicorn instance to serve Peak flask app. It's going to start up after the network is ready.
And then the service itself is going to be run under the user Tony, which we just created, and the group www-data, which is a standard group for serving HTTP requests. And the working directory is going to be our home directory, /hike, which is our flask app. The environment is going to be our Python virtual environment named Teton, and then inside of there is the bin directory. And then the actual command that's going to execute is the Gunicorn command, which we kind of experimented with just a bit ago. It's going to spin up three workers or three separate processes. It's going to bind a Unix socket called peak.sock, which is going to be created in our Flask app root directory. And as we saw before we have that new file in there called web_server_gateway_interface and inside of there there is an app variable, so that's basically what that means. And then this is an install, wanted by multi-user.target, that's, I believe that's so this is accessible for every user in the system.
So let's save that file and what we're going to do is start it up. So sudo systemctl start peak. And if you get this kind of error message, you can just reload as it's helping you out here. So sudo systemctl daemon-reload and then we'll try that again, start the peak app. And then we can do sudo systemctl enable peak, and what enable does is it's going to start this process automatically when the system starts up. And then we'll do sudo systemctl status peak and everything looks pretty good. It's active and running.
Installing and Configuring Nginx
So the next thing you want to do, since we've taken care of the flask app, we've taken care of the web server gateway interface, let's go ahead and set up the nginx web server, the proxy server. We can do that with sudo apt install nginx. And as is standard with most Nginx deployments, we'll make a configuration file with at the location of /etc/nginx/sites-available and we'll call it peak.conf.
So inside of the server block, we're going to listen on port 80 instead of the 5000 development port that we used before. Since I have a domain name, I'm going to specify that here, the non-www version and the www version. And then the location, we're going to map it to / so there's no path after you go to tonybony.com. We're going to include the standard proxy parameters, and this is the Unix socket that is going to be created inside of our flask app, and it's going to be called peak.sock which we referenced before in the systemd file. So let's save that.
And also as is standard with most Nginx deployments, we'll create a symlink from the nginx/sites-available directory to the nginx/sites-enabled directory. And what this does is basically publishes those changes as far as Nginx is concerned. Now we can test out our configuration file to make sure there's no errors with the configuration with sudo nginx -t and everything comes back looking good. And finally, we will restart Nginx with systemctl.
Configuring the Firewall with UFW
Okay, before we test anything out, we have to remember that we have a firewall running. Let's check it out: sudo ufw status. Right now we have the development port open, let's close that back down. So we'll do sudo ufw delete allow 5000. And there's a cool shorthand here: sudo ufw allow 'Nginx Full'. And what that's going to do is basically allow port 80, port 443, all the standard HTTP ports. So if we do sudo ufw status, you'll see that we have our standard SSH port and then Nginx ports as well.
Debugging the Nginx 502 Bad Gateway Error
All right, so our app is running, Nginx is up, and I think it's time to test that out. So let's go ahead and refresh this. Actually no, we're going to let's go directly, get rid of this port, go to http:// our domain name or IP address, hit enter, and we get a 502 Bad Gateway error. If you do run into this issue, it's a permissions issue and I'm going to show you how to debug it.
If we look at the, if we use the tail command which just shows the end of a log file at /var/log/nginx/ and there's a file called error.log, we'll see that, let's see, permission denied when it was trying to access the socket file. Okay, so we can change that really easily with the chmod command. We have to change the permission of the /home/tony directory, since we're serving it out of there, to 755, excuse me. So with that one simple change, we can refresh the page and now our flask app is being served via Nginx, via the web server gateway interface called Gunicorn.
Conclusion and Next Steps
That's about it for this one. If you want your Flask app to work over HTTPS, you can get a free SSL certificate with Let's Encrypt, and this video should point you in the right direction. Thanks guys, subscribe and I'll see you over there.