Python FastAPI Tutorial (Part 18): Deploy to a VPS - Security, Nginx, SSL, and Custom Domain
1:32:05

Python FastAPI Tutorial (Part 18): Deploy to a VPS - Security, Nginx, SSL, and Custom Domain

Corey Schafer 19.04.2026 4 649 просмотров 226 лайков

Machine-readable: Markdown · JSON API · Site index

Поделиться Telegram VK Бот
Транскрипт Скачать .md
Анализ с AI
Описание видео
In this video, we'll be learning how to deploy our FastAPI application to a VPS (Virtual Private Server) so that it's live and accessible on the internet. We'll walk through the entire process, starting with a fresh Ubuntu server and hardening it with SSH key authentication, a firewall, and brute force protection. From there, we'll set up Nginx as a reverse proxy, enable HTTPS with a free SSL certificate from Let's Encrypt, point a custom domain to our application, and use systemd to manage the app as a service so it starts on boot and restarts automatically if it crashes. By the end of this video, you'll have a secure, production-ready FastAPI deployment with a real domain name. Let's get started... Timestamps: Skip to 42:11 if your server is already secure Skip to 59:05 for the FastAPI-specific deployment steps The code from this video can be found here: https://github.com/CoreyMSchafer/FastAPI-18-Deployment-VPS Full FastAPI Course: https://www.youtube.com/playlist?list=PL-osiE80TeTsak-c-QsVeg0YYG_0TeyXI Linode (with Referral): https://www.linode.com/lp/refer/?r=d1a8982f8f82bfff0a130cd5061e50ebb9e07ac1 NameCheap (with Referral): https://namecheap.pxf.io/nyBgR WSL (Windows): https://learn.microsoft.com/en-us/windows/wsl/install Git for Windows: https://git-scm.com/install/windows ✅ Support My Channel Through Patreon: https://www.patreon.com/coreyms ✅ Become a Channel Member: https://www.youtube.com/channel/UCCezIgC97PvUuR4_gbFUs5g/join ✅ One-Time Contribution Through PayPal: https://goo.gl/649HFY ✅ Cryptocurrency Donations: Bitcoin Wallet - 3MPH8oY2EAgbLVy7RBMinwcBntggi7qeG3 Ethereum Wallet - 0x151649418616068fB46C3598083817101d3bCD33 Litecoin Wallet - MPvEBY5fxGkmPQgocfJbxP6EmTo5UUXMot ✅ Corey's Public Amazon Wishlist http://a.co/inIyro1 ✅ Equipment I Use and Books I Recommend: https://www.amazon.com/shop/coreyschafer ▶️ You Can Find Me On: My Website - http://coreyms.com/ My Second Channel - https://www.youtube.com/c/coreymschafer Facebook - https://www.facebook.com/CoreyMSchafer Twitter - https://twitter.com/CoreyMSchafer Instagram - https://www.instagram.com/coreymschafer/ #Python #FastAPI

Оглавление (3 сегментов)

<Untitled Chapter 1>

Hey there, how's it going everybody? In this video, we're going to be learning how to deploy our Fast API application so that it's live and accessible on the internet. So, we'll be deploying this to a VPS, which stands for virtual private server, and we'll talk more about that in just a bit. So, throughout this tutorial series, we've built a complete blog application. So, we have authentication, a Postgres database with Alembic migrations, S3 file storage for profile pictures, full async patterns throughout, and in the last tutorial, we added unit testing. So, our app is tested and production-ready. So, now let's deploy it to a server so that real users can access it. Now, I have two deployment tutorials planned. This one, where we deploy to a VPS, shows what's actually happening under the hood, and in the next tutorial, we'll do a containerized cloud deployment, which is a more managed approach. So, the reason that I'm doing the VPS tutorial first is because it shows you the fundamentals of what's actually happening when you deploy a web application, and these skills transfer to any VPS provider. So, whether you're using Linode, Digital Ocean, AWS EC2, or any other provider, the concepts are pretty much the same. Understanding these fundamentals also makes debugging a lot easier, even if you end up using a more managed service later. And honestly, not everyone needs containerized deployment. So, for a lot of applications, a simple VPS like this is going to be perfectly fine. Now, for this tutorial, I'm going to be using a Linode. Now, this isn't a sponsored video, even though they have sponsored videos in the past. This is just a service that I've used for a while now and haven't had the need to look elsewhere. So, I'll have a link in the description section below if you'd like to create an account and walk through uh so that you can walk through the video with me. But like I said, any VPS provider will work mostly the same way. Uh the concepts that we cover here should apply universally. Now, we're also going to use a real domain name because you probably don't want a live website with just an IP address. So, the name domain name that I purchased for this video is myawesomeapp. com, which I'll use for tutorials in the future as well. I'm using Namecheap for this. They don't have a dark mode, so sorry if I just blinded some of you guys. Uh so, we'll get HTTPS working with a free SSL certificate and also get that domain linked up. And we're also going to do some basic security hardening on the server because that's not really optional when you put something out on the internet. So, this will be a secure baseline that every server should have. So, with that said, let's go ahead and get started here. So, before we head over to Linode and set up our server, there's one small addition that I want to make to our application code. So, in production, platforms should probably have health checks. So, load balancers use these to know if your service is alive. Monitoring systems can also ping these regularly, and if a health check fails, then traffic gets redirected or alerts can fire off to you. So, this is a really common pattern that you'll see in production applications. So, for our blog application, the database really is the application. If the database is down, then nothing works. So, our health check is going to verify database connectivity. So, I have my main. py file opened up here. So, first, I need to add an import here, and I'm going to add this uh from SQLAlchemy. This is just going to be text. And now, down here, right after our router includes here, this is just going to be the very first route or endpoint for our application. I'm going to add a health check endpoint. Now, I already have this in my snippets file, so let me paste it in here, and we'll go over what it does. It's a very short and small route. So, let me grab this and paste this in, and now, let's talk about what this does here. So, what this does is it takes a database session from our dependency injection, and then it tries to execute a select one, which is an extremely lightweight query to our database. It doesn't scan any tables, it doesn't read any data or anything like that. It's just checking whether the database is reachable and that we can execute a query. And if that uh succeeds, then we return a simple JSON response with status healthy. But if the database is down and we get an exception, then we raise an HTTP exception with a 503 status code, which is the code for service unavailable. And 503 is the standard status code that monitoring tools and proxies recognize as basically saying, "Hey, I'm down. Don't send me traffic right now. " Now, with a more complex application, you might separate your health checks into different checks. So, you could have one that checks if a process is even alive, another that checks if it can serve request. But for our blog application, one endpoint that checks the database is more than sufficient here. So, let me save this really quick, and let me start the server if it's not running. It is running. And now, let me go to our web page here, and let's go to that health endpoint, and we can see that it says status healthy there. And we didn't have that include in schema set to false, so this will show up in our docs as well. So, if I head over here to the docs, then we should be able to see that we have a health check here. And yep, there it is. Now, before we set up a server, we need a way to get our code onto that server, and you have some options. So, you can put it in a repository and pull it on the server, or you can use something like SCP to copy your files up over SSH. I'm going to use GitHub, and I'll make this uh public repo uh since this is code that I'll link in the description of the video anyways, but private repos are also free on GitHub if you need that. So, let me switch over to GitHub here, and I'm going to create a new repository for our codebase here. Now, I'm going to click on create new repository, and now, I'm just going to do a uh fast I'm going to call this Fast API, and this is tutorial 18, and we will do deployment VPS will be the name of this repo. And now, I'm going to leave all these other options here as the default. Public repo, I'm not going to add a readme or anything like that. Let's just go ahead and create that repository. And then, it's going to open up this page here showing you a quick setup where we can create a new repo from the on the command line or push an existing repo from the command line. Now, I've already been tracking my project with Git, so I am going to grab this one down here that it is an existing repository, and this just adds this repo here on GitHub as our remote origin, and then pushes up to that. So, let me go to my terminal here. I'm within my project directory here, and I'm just going to paste in those lines, and that should push up here in just a second. Now, if you're not already using Git, then you could run Git a knit to initialize a new repository. Like I said, I've already been tracking my project with Git off-screen so that I could upload code for downloads throughout this series, but this main project uh didn't have a remote origin yet, but we just added that and pushed that up. Now, I've said this many times throughout the series, but if you don't have one, make sure that if you are using Git, that you are uh using a Git ignore file, and that we have our. env file in here as well. You can see that I am not tracking my. env. Uh that makes sure that all of our sensitive credentials are not pushed up to GitHub. So, with that said, I have pushed up uh the existing code onto GitHub, but we have not committed our new changes yet with that health check endpoint. So, to commit those, I can just do a Git add -a. And now, I will do a Git commit here with a message, and I'll just say that we added a health check endpoint. So, let me commit that, and now, I will do a Git push, and it should push up those changes. Let me go over to GitHub here, and let's refresh that repository and see if all those changes are there. I'm going to go ahead and check my main. py file here, and make sure that health check is there. And we can see that our health check is right there. Okay, so all of our code is now on GitHub. So, I'll go ahead and navigate back to my repository here, and then, I will leave this open here in the browser uh so that we can pull from this later. So, now let's head over to Linode, and let's create our new server here. We can see that I already have one Linode that runs my personal website, but we are going to create another one here. Now, I have a VPS setup guide that I use for all my deployments. I'll have a link to this in the description section below. I'm going to go ahead and pull this up, and I'll walk through this entire process and explain as I go. I occasionally update this and have updated it for this tutorial specifically, but for the most part, it's the base setup that I use for new servers. So, I'm going to have this on the screen at the same time as I have my browser open here. So, let me redo my windows here. And then, let me close that down, and I'm going to open up this VPS setup here. And now, I will have this take up about a third of the screen here or a fourth of the screen and then have my Linode here. Now, I'm going to go ahead and make the text a little bit smaller here so that I can read this. I usually like to have the text a little larger so that you all can read it easily, but like I said, I'm going to have this linked in the description section below. I really want you guys to be able to see the browser over here. So now, whatever service you're using, let's go ahead and create a new VPS. Now, on Linode, that is using this create Linode button here. So, I'm going to click that, and now we get our options to create a new server here. So, for the image, I'm going to choose Ubuntu 24. 04 LTS. LTS stands for long-term support, which means it gets security updates for a longer period of time. That's usually what you want on a server. For the region, I am going to pick Let's see, Dallas, Texas here, US Central. I'll choose that one because that works for me. You're probably going to want to choose a location where you expect your users to be closest to. So, if we scroll down here, then now we have to pick a plan for our Linode. You can see that they offer different options here. They have a dedicated CPU, a shared CPU, high memory, GPUs, things like that. I'm going to choose the cheapest option here because we have a fairly small application, and I am just going to choose this shared CPU plan here with 1 GB of RAM. This is $50 a month, which is fine for a small to medium traffic application, and you can always upgrade later if you need more resources. Okay, so scrolling down here, for this label, I'm going to give this a label of something specific. So, we'll call this Linode our fast API blog here. And now, we need to create a root password. Now, you'll want to use something strong here. I'm going to use the same test password that we've been using. So, test password one with an exclamation point if it even lets me use that password. We'll see here in just a second. And now, I'm going to leave everything else as the default here and just go down here to create Linode. So, actually first, let me make sure that they haven't updated anything since the last time that I've made this. I want to make sure that there are no additional charges or add-ons. You can see that you can add add-ons for backups. I'm going to leave that unchecked. Okay, so let's go ahead and create Linode here. Okay, so it seems like this has changed a little bit since the last time I've created one. We can just click no firewall here. It says not recommended, but that's fine for now. We are going to set that up ourselves. So now, let me click on create Linode. Okay, and our test password that might be the case. So, let's see here. Let me try my Linode one {exclamation point}, and let's see if that works. That doesn't work either. Well, here. Let's try how about fast API blog one {exclamation point}. That says strength is good. So, I will have to remember that. Fast API blog one {exclamation point}. Actually, I'm just going to put it right here in my snippets for now so that I don't forget it. And now, let's go ahead and create that Linode. All right, so our server is being created, and when it's done, it will give us a public IP address that we can use to connect. So, once that's finished, and we can see that it is finished here. Actually, it's still provisioning, but we already have our IP address here. I'm going to go ahead and wait for that provisioning to complete, and then we'll check back in. Okay, so it finished setting up our server here. We can see that it's running, and it gave us a public IP address. We also have SSH access here as well. So, let me open up our terminal, and we will SSH into that server as root. So, we can see that it's logging in there as root. Again, let me make this to where it's about a third of my screen here. Okay, so now, I'm going to paste in that SSH command so that we can log in to our server. It's going to ask if you want to accept a fingerprint. This only happens the first time that you connect to a new server. I'll just type in yes here and hit enter. And now, we need to paste in our password here. Now, I used fast API blog one here with an exclamation point. Okay, so now we are into our server here. Now, this is the first and the last time that we'll be logging in as root. So, let me explain why in just a second, but first, let's go ahead and update the system. So, I'm going to clear out what we have here. Now, let me go back to my VPS setup so that you all can follow along here. And for this setup guide, I've also added some additional comments and stuff. So, if you want to download that while we're walking through this, then you can read those comments as well. So, I'm going to go ahead and update our system here. So, that's apt update and apt upgrade with the Y option there. So, I'll go ahead and run that. So, what this does is it updates the package list and then upgrades all the installed packages. Now, we want to start from a clean updated state with all the latest security patches and bug fixes. So, I'm going to go ahead and wait for this to complete. Now, when you run this update, I made this comment over here. You may be prompted about modified config files like this SSH the configuration here. What I'm going to do since this is a fresh install, I'm going to install the package maintainer's version here. So, I will select okay on that, and now let that update complete. Okay, so that update completed. So, now the system is updated. So, now let's create a non-root user. The reason that we don't want to keep using root is that root has unlimited power on the system, and if root gets compromised, then an attacker has full control over everything. But using a regular user with sudo privileges, we limit the blast radius of any mistakes or security incidents. It's the best practice for any server. So, let's create a new user here. I'm going to use CoreyMS. So, I'll say add user CoreyMS. You use whatever new username you like. Now, it's asking for a password here. I'll go ahead and put in a strong password here. And now, it's going to ask for additional details. We can just hit enter here to accept the defaults. Okay. And now, we have created our new user. And now, I need to give this user sudo privileges so that they can run administrative commands that need to be run. So, to do this, let me copy this and paste it in here. So, user mod here modifies a user. This AG here means append to a group, and we are adding CoreyMS to the sudo group. So, let me go ahead and run that. And now, let me verify that worked. So, if I go if I type in groups CoreyMS, then we can see here that we get sudo there as one of those groups. So, now we have a user who can run admin commands when needed. Now, currently, we're using password authentication to log in to our server, and that's not ideal. SSH keys are much more secure. There's no password to actually brute force for an attacker, and the keys are much longer than any password, and the private keys stay on your local machine. So, let's go ahead and get this set up. So, this next part here is actually going to take place on our local machine. So, what I'm going to do here, let me minimize a couple of things there, and I'm going to open up a new terminal here on my local machine. And let's clear this out, and let me also scroll down here in our VPS setup guide. Okay, so what we have here is we have our server up here in this terminal, and we have our local machine down here in this terminal. Now, if you're on Mac or Linux, then all of these commands should be the same. If you're on Windows, then I'd highly suggest using something like the Windows subsystem for Linux or Git Bash so that you have access to Linux-like commands. If you plan on doing much work like this in the future, it's definitely nice to have on your machine instead of just using CMD or PowerShell. And I'll add links in the description section below for installing either WSL or Git Bash on Windows so that you can follow along exactly here. So, I'm going to go forward under the assumption that everyone has access to the commands that I'm about to write here. Okay, so let's go ahead and generate an SSH key. So, to do this, we are going to use this SSH keygen. And for the type here, we are going to use this ED25519. Now, that is just a modern recommended algorithm. It's faster and more secure than the older RSA keys that you'll see in some tutorials. Now, there's also this C flag here that we can use to add a comment to the key, which helps identify it later. Now, you can use whatever comment you'd like. The way I like to do this is I like to use my name with a dash and then my device and then the date. So, I'll do 2026 and let's see, what is today? For me, it's currently April 14th. So, I will go ahead and put that in here. And that just helps me remember exactly what each key is for and the date that it was created. So, I'm going to go ahead and run this. And now it's going to ask where I'd like to save it. So, I'm going to save this in my home directory in {dot} ssh. And then for the file name, what I'm going to do is I'm going to take the recommended file name here of ID_ED using that algorithm there. But then I'm going to add to it here. And again, I like to use a bit of a descriptive file name for my keys, just so I can look at it and know exactly which one it is. I like to do an underscore with the server name and then another underscore with my local device. So, the server name here, I'll just set as fast API blog and then another underscore and say that this is my iMac. So, let's go ahead and run that. And now it's going to ask for a passphrase. So, this is basically just a password. So, enter a strong password here. This protects your key if your machine is ever compromised. So, let me go ahead and confirm that password. Okay. And this says no such file or directory here. Maybe this doesn't use relative paths here. So, let me try this again. So, let me rerun that keygen. And now, instead of using a relative path, let me try this again here. And I will just type in the full path down here. So, let me paste in that file name that I used and rerun that. Overwrite? Sure. And now the passphrase again. Okay, so it created that key for us there. So, you can see that it has two keys for us here. We have this identification here and then we have this public key as well. Now, I need to copy the public key to the server. And there's a handy command for this and we can use SSH copy-id. And I'll scroll down here just so we are staying with our setup guide over here. And this is the step that we're on right here. Okay, so let me go ahead and clear my screen here. And now, what we can use here is this SSH copy-id. And then we have that {dash}i option there that just specifies the key that we want to copy. So, the location for my key was in {dot} ssh id and this was ed255 here. And now, I want to copy that public key to the server under our new username. So, that username was CoreyMS. And now, we can put our server address here. So, let me pull up our Linode here again to grab that public IP and paste this in here. So, now let's run this and it should push our public key up to that server. Now, it's going to ask for the password for that user that we created on our server. So, I'll go ahead and enter that. Okay, so that copied the key over. So, now let me add the key to my SSH agent. So, the SSH agent caches your key so that you don't have to specify the i flag with the key path every time you SSH in. So, we can see here that it's telling us to SSH in to the server and we have this i flag here with this big long key name here. If we add that to our SSH agent, then we won't need that part. And it also caches your passphrase so that you don't have to re-enter it repeatedly. So, the command for this is SSH add and I'm going to add that key. So, that was in my SSH folder there in my home folder and that was id ed with that algorithm fast API blog iMac. So, I'll run that and then we need to enter the passphrase. And we can see that was added. Okay, now this is important here. We're still on our local machine here in our terminal. Now, before we disable password authentication on the server, we need to make sure that our key-based login actually works. So, let's test that out. So, I'm going to SSH into our server saying SSH {at} and again, let me grab this IP address here and paste this in. And let's make sure that this works. Okay, so we can see that we were able to log in to our server without being asked for a password. That means that our SSH key is working. So, now let me verify the permissions on the SSH directory on the server because these need to be correct for key-based authentication to work properly. I could check them and see if they are already set correctly and they should be, but I like to do this manually every time. So, let me go ahead and run this. And this actually needs to be {dot} ssh here. So, all I'm doing is I'm changing the permissions for that SSH directory. 700 means read, write, and execute for the owner only on the directory. So, I will run that. And now I'll also do a chmod here with 600 and I will do this on that {dot} ssh directory with an asterisk there. And 600 means read and write for the owner only on all the files inside there. So, these are the correct permissions for SSH. Okay? So, now let me clear that out. And now that we're actually logged in with our SSH key, I'm going to go ahead and close down where we're logged in as root up here. And let me close that. And now I'll do everything from in here with my user. Okay, so now let's harden the SSH configuration here. So, we want to disable root login entirely and password authentication entirely. So, the only way to get into this server is with an SSH key. So, to do this, we can edit the SSH configuration that is at {forward slash} ect ssh and this is sshd_config. I'm going to use nano because I think that is easy for most people. Okay, and now I need to find and change several settings in this file. So, let me scroll through here. And the first setting that I'm looking for is permit root login, which is right here. We can see that is currently set to yes. We are going to set this equal to no. And this disables root login entirely. The next setting that we want here is pubkey authentication. I'm going to uncomment this here and we are going to set that to yes. That makes sure that key-based authentication is enabled. Okay, so the next one we want to find here is going to be password authentication. And I'm going to uncomment this and we are going to set this to no. So, let me change that to no. So, this disables password login. So, the only way in is with an SSH key. And I'll also go ahead and uncomment this permit empty passwords no here. I think that's the default, but we'll go ahead and set that explicitly there. And now, the last one here, we want this KBD interactive authentication. We'll make sure that is set to no. This disables challenge response authentication, which is another password-based method. So, let me go ahead and save all of those changes. So, in nano, you just hit control X to exit and then it'll ask if you want to save and I'll hit Y and then enter. And now we need to restart the SSH service for these changes to take effect. So, that is sudo systemctl. And we will do a restart of SSH. And let's run that. That restarts the SSH. Now, this is a critical step here. So, before I close this current system this current session on my server, I need to test in a new terminal that I can still get into the server. If something went wrong with our configuration, then we'd be locked out. But as long as I keep this session open, then we can fix any issues before we are locked out. So, let me go ahead and on my local machine here, let me open up a new terminal here. And now, on my local machine, I want to make sure that I can still SSH in to that server. And that logged us in without asking for a password, so we're good. Uh now, the only way to get into our server now is with uh SSH keys. No one can brute force a password because password authentication is completely disabled. So, let me go ahead and close this extra terminal session here and go back to our server here. Okay, so let me catch up with our VPS setup here. And now, let's continue setting up this server here. So, now let's set the time zone. So, I prefer to set servers to UTC because it keeps logs consistent and avoids confusion with daylight saving time. So, let me check what it's currently set to. So, let me just grab this command here and check the current time zone. It looks like that's already set to UC UTC, but just to be explicit here, uh I can grab this command here. If yours isn't set to UTC, then we can run this command here to set that. Okay. And then I can verify that is still good and it is. Okay, so now, let's set the host name of this server. So, this makes logs and command prompts uh clear so that you know which server you're on. So, I'm going to uh grab this here. And this is on a word wrapped line here. Okay, so I'm going to set the host name for this server to fast API blog. So, let me set that. And now, we can verify that just by typing in host name and we can see fast API blog there. And I also need to add this to the host file. So, I'll say sudo nano and this is in ETC hosts. And within here, right under localhost, I am again going to grab the IP of our Linode here. And paste that in. And then, we are going to uh set that there as well. That was fast API blog. And to save that, I'm going to hit control X, uh Y to save yes, and then hit enter. Okay. Let me clear out what we have already. Okay, so now, let's set up our firewall. And we are going to use UFW here. Uh UFW stands for uncomplicated firewall and it's usually pre-installed on Ubuntu, but let's make sure here. So, I'm going to do a sudo apt install UFW with the Y flag to accept anything that comes our way. Okay, so it looks like it was already installed. Now, the first thing that we want to do here is set the default policies for our firewall. So, we want to deny all incoming traffic and allow all outgoing traffic. And then, we'll only open the specific ports that we need. So, I'll do a sudo UFW default deny incoming. So, I'll run that. And now, what I want to do here is a another default and this is going to be allow outgoing and run that. And now, this is another critical step here. We need to allow SSH before we enable the firewall. If we enable the firewall without allowing SSH, then we would lock ourselves out. So, I'll do a sudo UFW allow and we want to allow SSH. And we also need to allow HTTP and HTTPS for our web server. So, another allow rule here. We will do 80 and that is going to be for TCP. Enter. And then, also 443. Okay, so now, let's enable our firewall. So, that is going to be UFW enable. And it's going to ask if we want to confirm. I will say yes. And we can see that it says firewall is active and enabled on system startup. So now, let me verify the rules here. So, I can say UFW status and verbose. So, let's take a look at these. And we can see here that the default is deny incoming, allow outgoing, and then only SSH, HTTP, and HTTPS traffic can reach our web server. Everything else is blocked. Okay, so now, let's set up something called fail2ban. Uh so, fail2ban monitors log files for suspicious activity and bans IP addresses that failed too many login attempts. So, it protects against brute force attacks. Even though we disabled password authentication, it's still a good practice to have something like this running. So, let me go ahead and install this. So, I'll just grab this install command from over here. Okay, so once that's installed, we need to create some local config files. Now, you should never edit the dot conf files directly because they get overwritten during updates. So, we are going to create our own local ones. So, to do that, let me go ahead and copy those commands from over here. So, I can paste this in. And let me make this just a little larger here so that we can see everything. So, we are copying this file here and creating a local version of that at dot local. So, let's go ahead and run that one. And I will also create a version of this one as well. So, fail2ban. local and jail. local from fail2ban. conf and jail. conf. So, create those. And now, let me edit that jail. local file. So, I'm going to open that up in nano here. And now, I'm going to scroll down here until I find uh the default section here. So, let me keep scrolling down here. So, here's the default section and I need to find the ban action here. So, let me find that. Hopefully, I don't overlook it. And let's see. If I don't find it here in a second, then I will just do a search. Okay, so let me do a control W here for search. And let's search for ban action. Okay, so that is right here. And let me confirm that I am in the default section here. So, let me go ahead and scroll up and confirm this has multiple sections here. It is in that section. Okay, so that was right down here is ban action. This is what we need. So, we need to set this to UFW. And that is going to integrate with our firewall. So, I'll set UFW for ban action there. And now, let's find the SSHD section. So, let me scroll down here until I find the SSHD uh section here. Okay. So, now, I'm going to add in a few settings here. So, first, I'm going to uh add enabled equal to true. The port I'm going to have as SSH. For filter here, I'm going to have that equal to SSHD. That log path is fine. That back end is fine. I will also set max retry here. I'm going to set that equal to three. I'm going to do find time equal to 10 minutes. And then, ban time equal to 1 hour. So, what this is saying here is if someone fails to log in three times within 10 minutes, then ban them for 1 hour. So, that's a pretty reasonable setting there. So, let me go ahead and save that and exit. So, I will hit yes there. And now, let me start and enable fail2ban. So, I have those commands pulled up over here. Let me go ahead and start that. And then, I will enable that. And now, let's look at the status here. So, let me see the status. We can see that is running. Okay, so that is good. Okay, so let me close out of that and let's clear the screen here so that we have some more real estate. And now, let me check banned IPs. After some time, this will probably start to fill up. Let me see if we already have any. Okay, so we can see here that we have some currently failed and some total failed. Uh let me wait a little bit of time here and let's rerun this again. Okay, so no more action there right now. Now, you might actually already see some banned IPs here. Fail2ban reads recent logs on startup and bans past offenders. Now, this is kind of a teaching moment because we've only been online for maybe 30 minutes or so and you're already going to see bots probably trying to break in. And this is why security hardening isn't really optional, it's necessary. So there are automated bots constantly scanning the internet for vulnerable servers and if you don't have these protections in place, then it's only a matter of time before someone gets in. All right, so the last thing for our security setup here, uh let's enable, let me go ahead and fix my screen width here. Uh so let's enable automatic security updates here. So this is Ubuntu's official tool for keeping security patches up to date automatically and it only applies security updates, not all pack- packages. So it's safe to leave enabled. So I'm going to uh copy this from my setup guide here and paste this in, unattended upgrades install with the Y flag there to accept everything. And then let's enable those automatic updates. So again, let me grab this command here and DPKG reconfigure uh with those options there. Let me run that. We want to select yes when it asks when we are prompted. So I'll go ahead and run that. Okay, so now let's clear out the screen here. Okay, so I also have a command here to verify that configuration. So let me cat this out here. So we can see that it's set up to automatically check for and install security updates. Uh there's also an option to configure automatic reboots for kernel updates. If you want to be completely hands-off with your server, uh you'd edit the unattended upgrades config file and set automatic reboot to true with a specific time. But I'm going to skip that here. But if you want to be hands-off though, then feel free to set that up. So now our server stays fairly automatically. So that's part one. So we have a non-root user, SSH key authentication, the firewall configured, fail2ban protecting SSH, and also automatic security updates. And everything that we've done so far applies to almost any server that you're going to use for a website regardless of what you're deploying, whether it's a Python app, a Node app, a Go service, whatever. This is just a good baseline server setup. So now we're going to

Skip to.if your server is already secure

start making choices that are specific to our application stack. So now for our application, let's install Nginx. So Nginx is a reverse proxy that sits in front of our application. It handles SSL and HTTPS, serves static files efficiently, and can do load balancing and caching. And it's the industry standard for this kind of thing. So let's install it. So first I'll do a sudo apt update. And now let's go ahead and install this. Again, let me catch up here in my VPS setup guide here. Okay, so I'll just grab the command to install Nginx and let's run that. Okay, and once that is installed, we want to start and enable Nginx. So I will start that and now we will enable this. And now let's check the status here. So I'll check the status and we can see that is enabled and actively running. Okay. Okay, so now let's test this by visiting our IP address in our browser. So I'm going to open up the browser here. I'm going to copy our public IP address. Looks like my Linode is refreshing there. Okay, so I'll copy that public IP address and now let's test this here in our browser. And we should see that Nginx welcome page and there we have it here. So Nginx is running. Let me make this a little larger so everyone can see. Okay, so this is nice. Uh we have this now showing up on our IP address, but we probably don't want to use an IP address. Let's go ahead and use a domain name instead. That's likely what you're going to want. Now before we configure Nginx for our domain, we need to set up the DNS so that our domain name actually points to this server. So my domain is myawesomeapp. com. It currently points somewhere else. So we'll switch it to point to our new server. And this is a common scenario when you're moving from uh static hosting to a dynamic application. So let me head over to Namecheap. That is my domain registrar. And here I have the settings pulled up for myawesomeapp. com. Uh now you have some overall settings here. What you're going to want to find is your DNS settings. So on Namecheap, that is over here in advanced DNS. So I clicked on that and then we have these records here. Now your registrar might call it something different. Uh mine is advanced DNS. Yours might be DNS management or DNS records, something like that. But they will have a place uh where you can manage your DNS records. So I have this pointing somewhere else for now. I think I have this pointing to GitHub pages maybe. I'm not exactly sure, so don't quote me on that. But I am going to delete the existing A records here and if I had any CNAME records, I would delete those as well. So let me go ahead and delete these. Uh looks like I had an issue here. Maybe I'm not logged in anymore. Okay, I'm going to log back into my account and then I'm going to fast forward to where I'm back on that page. Okay, sorry about that. It logged me out. So now I'm going to delete this these existing A records here. So I will delete all of those existing ones and now I'm going to add in some new ones here. So this is going to be an A record. So for the first record here, I'm going to do this at symbol here and that is for the non-www version of your website. So for this, it would be myawesomeapp. com. And now I'm going to set the IP here. So let me go back and grab the IP and then paste that in and we will save that. And now let's also create an A record here for the www version of this. And again, it's just going to be that same IP. And I will save that. Okay. So now these changes have to propagate. So DNS propagation can take up to 48 hours, but it's usually much faster than that. Often, you know, 5 to 30 minutes, something like that. So let me go back to my server terminal here and test if this has propagated yet. So again, let me catch up here in my VPS setup and let's see. We are all the way down here to uh this part here. So we can see here that we're doing a dig on our domain and this short here just gives the IP without extra DNS details. So I'm going to do a dig on my myawesomeapp. com. You would use whatever domain that you are using. And I'll add in that short there. So let's run that. And let's also run that for the www version as well. So let me go ahead and run that. And this should return our server's IP address. So uh 96. 126. 113. 103. Let me check if that is our server's IP address and it looks like it is. Okay, so that has already propagated. Now if those don't return your server's IP address, then you might just need to wait a bit and try again and wait for that to finish. Okay, so now that our domain points to the server, let's configure Nginx. So I'll create a site configuration file here. So let me grab this from my VPS setup here. Now instead of your domain. com, I'm going to put in my domain here, which is my myawesomeapp. com. Make sure I spelled that correctly. And since this is a lot of typing here, I have an initial configuration file for Nginx in my snippets here. So let me grab this. I can Actually, I'll Actually keep that uh password there for now. So let me grab this initial configuration here for Nginx and paste this in. And I didn't grab this comment here. I actually wanted this comment as well. So let me go up here and put that in as well. So what we have here for this initial configuration, we have two server blocks. So this first one here redirects www to non-www. So basically, if someone visits www. myawesomeapp. com, then they get redirected to myawesomeapp. com. That's just my personal preference. You could set it up the reverse way if you'd like. So the second block here is our main server block that handles the actual request. For now, it's just serving the default Nginx page. We'll update this later to point to our FastAPI application. So let me go ahead and save this. And now I need to enable this site by creating a symlink from the sites-available to sites-enabled. So to do this, I'll do a sudo ln -s, and this is in etc. nginx, and that is sites-available. And we want to grab that myawesomeapp. com, put the symlink in etc. nginx, and this is going to be sites-enabled. So, let's go ahead and run that. Now, if I look at what's already in that sites-enabled, then we should be able to see the default site is already in there. So, let me look in here. We can see right there is that default site. I'm going to go ahead and remove that since we don't need it anymore. So, I'll do a sudo rm, and we will go ahead and remove that. So, that's nginx, that's sites-enabled, and default. I will go ahead and remove that. Okay. I'm going to go ahead and clear the screen here. So, now, let me go ahead and get back to having my VPS setup here on the side, and let me scroll down to where we currently are. Okay, so now, let me test the configuration to make sure that our nginx configuration has no syntax errors. So, to do that, we just do sudo nginx with that -t option there. We can see that the syntax is okay, and that the test is successful, so that's good. So, now, let's reload nginx to pick up the changes. So, let me go ahead and reload that, and that is that command there. So, I'll run that. Okay, so now, let's set up HTTPS with a free SSL certificate from Let's Encrypt. So, HTTPS encrypts traffic between users and your server. It's required for the modern web. It's an SEO ranking factor, and browsers show warnings on plain HTTP sites, so this is an essential step here. So, we're going to use Certbot to get our certificates. So, let me install that, and we will install that uh with Snap. So, again, I'm going to do a sudo apt update. Once we are updated there, I'm going to install Snap, and that is with sudo apt install snapd with the -y option to accept anything. Okay, and now, we need to do this sudo snap install core. Let's run that. And once that is installed, then we can do this sudo snap refresh core. So, let's refresh that. Okay, so that looks good. So, now, let's remove any old Certbot if one exists. So, let's run sudo apt remove Certbot, and there should not be any to remove. And now, let's install Certbot. So, let me clear out the screen here. And now, this is sudo snap install classic Certbot. So, I will run that. Now, this may take a little bit of time to install here, so I'm going to go ahead and fast forward to when this is finished. Okay, so that installed there, and now, I'm going to create a symlink here for easier access. So, from this snap bin Certbot into user bin Certbot. Okay. Now, before we run Certbot, it's important to make sure that DNS has actually propagated. Certbot needs to verify that your domain points to this server. If DNS hasn't propagated yet, then the verification will fail. Now, it looked like mine already had. I'm going to hit up here until I find that command again. Okay, so if I run that, then that is my server IP. If yours isn't showing that yet, then you'll have to wait until that has propagated. Now, mine is showing my IP. I've never had mine take very long, but if yours isn't showing yours yet, then you may have to wait anywhere from a few minutes to a few hours. So, let's go ahead and obtain our certificate here. So, to do that, I'm going to grab this command here to obtain our certificate, and I'm going to go ahead and replace the placeholder here of your domain with my awesomeapp. com. You would use your domain, obviously. So, I will put that in here as well. And now, let's run that. And now, you're just going to have to follow some prompts here. So, it's going to ask for an email. I'll go ahead and put in my email here, and then we're going to have to agree to the terms of service. So, I will hit yes to accept those. And the second one here is about sharing your email address. I don't think you have to accept this one. I'm going to go ahead and accept it. I don't think you have to accept that one, though. So, I will accept that, and oops, it looked like that failed. You guys probably noticed this. I may have typed my domain name wrong there. I did. So, I didn't do myawesomeapp. com Let me make this a little larger so that we can see everything on one line. So, I'm going to rerun this now. And again, sometimes in the terminal here, some text can run together here. Like, I think that this is incorrect, but I think that is just the output. I think that what I typed in is actually fine. So, let me try that just one more time just to make sure. Okay, it looks like that was already installed. I'm just going to hit C there to cancel. I just wanted to make sure that was running okay. So, now, that Certbot is going to automatically configure nginx with the SSL certificate. It modifies the nginx config file that we created to add the SSL settings. So, let me test that automatic renewal works. Certificates expire after 90 days, but Certbot sets up a timer to renew them automatically. So, to test that this will work, we can do sudo Certbot renew, and we just want to do a dry run here. So, I'll do a dry run, and let's test this. So, you can see that it simulated a renewal of an existing certificate for myawesomeapp. com and www. myawesomeapp. com, and that it succeeded. Okay. So, now, let me also verify that the renewal timer is active. So, to do that, I can do this systemctl list-timers, and we will grep for Certbot. So, if I run that, then we can see that timer is active there. So, now, our site should have HTTPS. So, let me verify that by visiting the site in a browser here. So, let me clear out our screen here, and now, let me pull up our browser, and let's go back to our website. So, I will just go directly to our website there. Okay, so we are getting an nginx page here, but it is 403 forbidden. I'm not too worried about that right now. I just want to make sure that we are on HTTPS here, and we are. Let me also check the www. version of this, and it gets redirected. Okay, so that's good. And if I click up here, then we can see connection is secure. So, we do have a valid certificate here. So, that all looks good. All right. Okay, now, the reason that we're getting a 403 forbidden there, that is because our nginx is pointing to this root index. So, let me see if anything is in here. So, if I do an ls, I believe that's pointing to var www, and let's see if there's anything in the HTML folder here. Okay, so that is named something other than index. html. Now, this is unnecessary here because we are going to change what this is pointing to anyways, but if we wanted to get this actual webpage running there, then we could just move that. So, we could move that var www html index, into that same folder and just rename it to index. html. So, index. html, let me run that. Let's reload this in my browser here. Now, we can see that we are getting that default nginx page, and our site is secure. So, the secure part was the thing that I was most worried about. So, that is looking good. Our certificate is working. Okay, so now, the big part. Now, let's get our FastAPI

Skip to.for the FastAPI-specific deployment steps

application running on the server here. So, first, let's install Python and some required packages. So, let me go down here in my VPS setup guide here. First, we will run a sudo apt update, and once this is done, now let's install some required packages here. So, I'm going to install Python 3, Python 3 pip, Python 3 venv, and get. And with the y flag there to accept everything. So, let's install all of those, and I will let this finish. Okay, so now I'm going to clear the screen here. And now, let's verify the Python version. So, I'm going to do Python 3 {dash} version. And we can see that we should be on Python 3. 12 or higher [snorts] on Ubuntu 24. 04. Okay, so now let's install UV, which is the fast Python package manager that we've been using throughout this tutorial series. So, I have the curl command here for installing this. So, I'm going to copy that and paste that in here. This is the curl command that is listed on their website. So, I'll go ahead and run that to install UV. And once that's installed, I need to reload my shell to pick up the new path. So, to do that, I'm just going to source my. bashrc file. So, that is {dot} bashrc. Okay, so let me run that. And now, let me verify that UV is installed and that our new path was picked up. So, if I do a UV version here, whoops, and I put a space there where we didn't want one. So, let me clear that out. So, UV version. Okay, and we can see that we are on 0. 11 of UV there. Okay, so now, let's install and configure Postgres here. So, again, I'm going to do an sudo apt update. And then, we will do a sudo apt install Postgres here. There are going to be two of these. We have PostgreSQL and PostgreSQL contrib with that y flag there. I'll install those and wait for those to finish. Okay, and once that is installed, then we want to start and enable Postgres. So, I will copy these commands here. So, systemctl start PostgreSQL, and then we will enable that as well. And after that, let's check the status to make sure that is running. And it is running, so that is good. Okay. So, now that we have Postgres running, now let's create our database user and database. So, I'll switch to the Postgres user and open the Postgres prompt. So, to do that, I will do a sudo u of Postgres, and then run psql. And now, I'll create a user and a database for our blog application. So, I will do a create user, and we will call this blog user. And we'll do a with password here of blog pass. And those are the same credentials that I used in my local development as well. So, let's run that. And now, let's create a database. So, I'll do create database, and we'll call this blog. And we will set the owner equal to that blog user. And let's run that. And now, let's do a grant all privileges. And let me spell this correctly. So, grant all privileges on database. And that will be the blog database. We will grant those privileges to that blog user. Okay, and now we can exit out of Postgres here. Now, obviously, you would want to use a strong database password for your application. I'm using a simple one here for the tutorial. Okay, but now, let's test that connection. So, I will do a psql, and we will use that user that we just created. The database is going to be blog, and the host here we will do as localhost. And it's going to ask us for a password. That was blog pass. And we are logged in to that database. Okay, so that connection works. I'm going to quit out of that. And the credentials here that you use on your production server, we will set those in our {dot} env file here in just a bit. So, now, let's deploy our application code. So, first, I'll create the application directory. So, this application directory, let's put this in. I will do a make dir here with a {dash}p so that it creates everything that it needs to create. Let's put this in var www, and let's call this fast API blog. And let me make sure that I'm caught up here in our guide. Okay. And now, we want to change the owner here of that to our current user. So, I will run this command here. So, now we need to get our code for our application onto the server. Now, you can either clone from a Git repository, or you can copy the files up with scp. I pushed to GitHub earlier, so I am going to use Git the GitHub URL here. So, let me grab that repo URL from my GitHub. So, here I have that repo open. I am going to grab that URL from here. Okay, and now, going back to our server here, let me cd into that directory that we just created for FastAPI blog. So, I'm now cd'd into that directory. Now, I'm going to do a Git clone of that Git URL, and then I'm going to clone this into the current directory. So, let me go ahead and run that, and it should clone our code here. So, now, if I do an ls, we should see all of our application code. Now, if you prefer to copy from your local machine, you can use scp. Just note that scp with the star wildcard won't copy {dot} files. And that's actually fine. We don't want to copy the {dot}env file from our development environment anyway. That should be created fresh on the server with production values. And our virtual environment at {dot}venv, that'll get created by UV sync here in just a second. So, feel free to use secure copy if you want, but I'm using Git clone in this video. Okay, so let me clear out what we have here. Now, one thing that I want to do here, I don't know if I have this. It doesn't look like I have this in my VPS setup guide here. But, one thing that I want to do is set secure file permissions. When you copy files with scp or clone with GitHub, the permissions can be inconsistent. So, I like to lock everything down, and then open up only what needs to be open. So, first, I'm going to do a find here, and the type that I want to find is files. And then, I'm going to do an exec to run a command here. And I'm going to do a chmod of 600. And we will go ahead and run that. Oops, and I need that outside of those brackets there. So, let me run that. And now, I'm going to do the same thing except for directories. And I'm going to set this to 700 here. So, this first command here, that gives all files read write for the owner only. And the second one here, all directories read write execute for the owner only. This is a deny-by-default approach. Now, we need to open up what Nginx needs to serve static files. Nginx runs as the www-data user, and needs to be able to traverse our directory and read the static files. So, what I'm going to do here is a chmod of 755 on the current directory. And then, I'll do a chmod of 755 on static. Okay. Now, I thought that I had that part in my VPS setup guide here. Let me just go ahead and do one here for secure permissions. And let me go ahead and paste these in here. Okay, so I'll save that. And now, back here on our server, now let's install our Python dependencies. Now, since we're using UV, and since we have this UV lock file here, we can simply say UV sync, and it should create and install everything that we needed to install. Okay, so what UV did there is it will create the virtual environment and install everything from our pyproject. toml file and our lock file. Okay, so now we're going to create our {dot}env file with our production settings. But first, let me generate a secret key. So, to generate a secret key, I'm just going to run Python 3 and have this run a command here. The command that I want to run is import secrets. Then, we'll use a semicolon just to run all this on one line. And then, I'm going to print out a secret key here. So, we can use that secrets module, and then do a secrets. token_hex of 32. So, let me run this, and we can see that we get this big long random secret key here. So, now let's create our. env file that has all of our production settings. So, I'll just use nano for this. So, I'll do a nano. env to create this file. Now, first I'm going to just paste in that key that we just created. We will use that in just a second. And now, I'll fill in all of our environment variables. Again, this is a bit of typing. So, I'm going to grab this from the snippets file and then we will go through this. So, I'm going to grab all of the environment variables from here and let's paste these in. And now, for this secret key here, I'm going to grab that and let's remove that. So, I'll just go ahead and cut that out. And now, uh we want to put that secret key that we just created right here. So, I will delete this placeholder here where it says paste your generated key and I will paste in our secret key there. Now, the rest of the values that I have in here, these are my own username and passwords for stuff that I've been using throughout this series. And don't worry about them being visible. I'm going to deactivate this stuff before the tutorial goes live, but you'll need to go through here and fill out your own credentials for everything that we have in here. So, for example, you're going to want to change your database URL to your database user and password and your own database name here. You're going to use your own secret key. If you're still using mailtrap, then the mail server should be the same, but you're going to want to use your mail username, your mail password. For the front end URL here, I have this set to my domain name that I'm using. You'll want to use your domain name, your S3 bucket name, and your S3 key ID, and your S3 secret access key. Now, one thing I have changed in here, you'll notice before in the series, I changed the mail port from 2525 to 587. We've been using mailtrap throughout the series and 2525 was the port that we used for their sandbox, but 587 also works with mailtrap and it's the standard SMTP submission port that pretty much every production email provider uses. So, if you were to sign up for a production email service like SendGrid or AWS SES or Mailgun, then you would just update the mail server, the username, and password to match what they give you and port 587 will already be correct there for you. Okay, so I'm going to go ahead and save that. So, I'll exit out of that and save the changes. And now, let me make sure that the. env file is secure here. I'm going to do a chmod of 600 on that. env file and that restricts it to the read write for the owner only. Okay, so now, let's run our database migrations. So, to run our database migrations, we can run uvi run alembic upgrade head. So, we can see that it ran through those migrations and our database tables are created. Now, let's do a quick manual test to make sure the application actually works. I'll temporarily open port 8000 on the firewall. So, I will do a sudo ufw allow and I will do a 8000 here with TCP. So, let me allow that port and I will type in my password there. And now, let's start the app. So, I can do a uvi run. Let's do fast API run and let's do a host of 0000. Let me make sure I have a space in there. And also, let me make this a little larger here. We want to do this on port 8000. This is just for a test. So, let me run this and that should spin up our server. Okay, so we can see that this is running on 0000 port 8000. That host of zeros there, that makes it accessible from outside the server, not just localhost. So, let me visit this in the browser. So, let me grab the IP address here of our website and let me paste this in and we want to go to port 8000. Okay, so it looks like our application is working here. We have a fresh database, so there are no posts yet, but let me check that health route that we added at the beginning here. So, let me check that health endpoint. We can see that we get a status of healthy here. So, the app is working and we do have a database connection. So, now let me go back to the server here and I will go ahead and stop that and clear out our screen. And now, let me remove that temporary firewall rule that we put in place since we don't need it anymore. So, instead of a sudo ufw allow, I'm going to do delete allow of that 8000 port there and run that and everything is now going to go through Nginx instead. Okay, so now let's set up systemd to manage our application. Systemd is the system service manager on Linux. It handles starting, stopping, and restarting services. It starts our app on boot and if our app crashes, systemd will automatically restart it. First, let me check how many CPU cores we have because that affects our worker settings. Now, since this is the cheapest Linode that we rented, then we can see here that we just have one CPU core. Now, for async workers, which is what we're using with FastAPI, you want one worker per core. This is different from the old formula of you take your core and then you multiply it by two and add one. That is for synchronous workers. Since our app is fully async, one worker per core is the right setting. So, let me create the systemd service file. So, we will do a sudo nano and this is etc and this will be systemd system and then we will call this fast API -blog. service. So, let me create that file. And now, in here, again, this is a bit of typing. So, I'm going to grab this from my snippets and then we will walk through this. So, let me look at my snippets here. We can see here that I have this systemd service file. Let me paste this in here and now let's walk through this. So, in the unit section here at the top, we have a description and we tell systemd that our service should start after the network and Postgres are available. So, we can see here after network and after Postgres service. The wants directive means that we prefer Postgres to be running, but our service won't fail if it's not. In the service section here, we set the user to queryms and the group to www-data. The working directory is our application directory. We set the path to include our virtual environment's bin directory so that it can find our installed packages. The environment file points to our. env file, so all those environment variables get loaded and then exec start is the actual command to run our application. So, we use the full path to the fast API command inside of our virtual environment. And for us, uvicorn has already created that virtual environment. So, that is the full path to that fast API command. So, then we're doing a fast API run. The workers flag should be set to your CPU core count. Ours was just one. The host is set to 127. 0. 0. 1 because Nginx will proxy traffic to us on localhost. The port is 8000 and proxy headers tells uvicorn to trust X-Forwarded-For headers from Nginx, which is necessary for our application to see the correct client IP and protocol. Restart always means that if the process dies for any reason, systemd will restart it. And restart sec equal to five, that gives it a five-second delay before restarting. Now, this install section down here, this wanted by target means that this service starts when the system boots up to the normal multi-user run level. So, let me save this and I will accept those changes and hit enter there. And now, let's reload systemd to pick up the new service and then start and enable those services. So, let me reset my screen sizes here and go back to our VPS setup and make sure that we are all caught up here. So, we are way down here in the fast API with systemd section here. I've already done that part. Okay, so here is where we want to reload system D and start the service. So, I will run this pseudo system CTL Damon reload. And now we want to start that service that we just created. So, we will do system CTL start fast API blog. And now let's enable that service. So, we will run enable on that. Okay. And now let's check the status of that. So, I will check the status here. And we can see that is enabled and that is running. Okay, so that is good. Okay, so now let's update our engine X configuration to act as a reverse proxy for our fast API application. So, let me open this file. So, we'll do a pseudo nano and that was in ETC engine X and that is sites available and then my awesome app. com. So, let's update this. Now in here, I'm going to clear the existing content and I'm going to replace it with our full production configuration. So, I will use the let's see on a Mac, I think this is alt T to clear all of that out. Okay. And now I'm going to paste in the full configuration from my snippets and then we will go over this. So, this is going to be a lot here, but let me grab all of this and we will go over this. And this is the last snippet for this tutorial here. So, let me get this in line here. Let's take a look at this here. Now, there's a lot in here and engine X configuration could be an entire tutorial on its own. A lot of this is standard boilerplate that you'll find in most engine X templates like the SSL certificates, security headers, and timeouts. So, rather than going through every line, let me just cover the key parts for our application. So, we have three server blocks here. We can see server, server. Three main server blocks. The first two handle redirects. So, the first one here is www to non-www and the second one here is HTTP to HTTPS. The third block is our main HTTPS server block here. And this is where the most important stuff is going to be. Now, again, like I said, some of this is engine X boilerplate that I found online, but we can see here some of these things. The static files location, this tells engine X to serve files from our static directory directly without passing them through fast API. This is much more efficient because engine X is optimized for serving static content. Now, the main location block here, this is the reverse proxy. This is where engine X takes incoming request and forwards them to our fast API application running on port 8000. So, we pass through headers like host, X real IP, and X forwarded for so our application knows about the original request. And this client max body size set to five here, that matches our application's upload limit for profile pictures. So, I know that this is a lot and can look a little intimidating. Like I said, engine X configurations could be an entire tutorial just on their own. So, I'm going to save what we have here and hit yes on that. And now let me test that engine X configuration. So, to do that, we can do engine X -t there. The syntax is okay. The test is successful, so that's good. And now let me reload engine X. And also let me make sure here that we are where we need to be here. Okay, so now let me reload engine X here. And now with that reload, our fast API application should now be live at my awesome app. com. So, let's verify everything here. So, let me open this up in the browser and let's reload this page and we can see that works. And also we have a secure connection, so that's great. So, let me test our health check endpoint here. So, I'll do forward slash health and we can see that it says status healthy, so that's great. Let's check our docs endpoint here for our API documentation. Okay, and that all looks good. So, now let me go back to the main page here. And now let me create a user through the register page. So, in here, I will create a user of Corey M. Schafer. I will put in my email address here. And then for the password, I will just do test password one and then I will use register that. It says that the account was created successfully. Okay. So, now let me log in and create a post here from our front end. So, I will log in with our email test password one. Go ahead and log in. We logged in successfully. Let's do a new post. We're now live deployment worked. All right. So, we're now live created successfully. Okay. And we can see that there. And now let me also upload a profile picture to make sure that S3 is working as well. So, right now we have our default profile picture. So, let me choose a profile picture here. So, I can just go to desktop and I will choose our fast API blog here. And for populate images, I don't know. Let's choose my dog Bronx here. Okay. So, let me upload that and we can see that says that it was updated successfully. And if I go back to the home page, we can see that we now have our new profile picture here. If I open that in a new tab, then we can see that is coming through from an AWS S3 URL. So, the picture uploads are working with S3 as well. Now, if you did sign up for an email service or if you're still using mail trap, this would also be a good time to test that the password reset flow works just to make sure that that's working in production as well. So, everything seems to be working here. Awesome. So, at this point, we're basically deployed. So, let me go ahead and go back to my server really quick and resize our pages here. Okay, so really quick, let me also go over some useful maintenance commands here. So, to check our application status, we can use this system CTL status on that service that we created. So, if we look at that, we can see that it is running. To restart after some code changes, we could do a system CTL restart on that service. To view the application logs, we can do this journal CTL with that fast API blog. This command here. Again, I will have all of these in the downloadable code for us here, but we can see here that we get some logs here. Now, we have some requests coming in here. Some of these are probably just bots looking for certain things. So, I'm going to go ahead and kill that. Now, when you need to update your application, the process is pretty simple for how we currently have it set up with Git. So, you would push your code changes to your Git repo. And then here on the server, you would just navigate to your project directory. So, I'm actually already navigated here. It's in this var www fast API blog. And then we would just do a Git pull to pull in those code changes. Then you could do a UV sync. And then after that, if you needed to run some Olympic migrations, then you could run those with Olympic upgrade head. And then lastly, you would just restart the service. We just saw how to do that. So, we would restart that service and then that would pull in your latest code changes. So, at this point, our server is locked down with key-based SSH access. We have a firewall and brute force protection. We have HTTPS with automatic certificate renewal. Engine X is handling traffic and our fast API application is managed by system D, so it starts on boot and restarts if it crashes. Now, you might be thinking, "Okay, that was a lot for pushing a website to a server. " But now that we have our process down, if we wanted to automate this for future deployments, then we could use a tool like Ansible to script everything that we just did. So, Ansible lets you define your server setup in a configuration file and run it against any number of servers. So, you'd take everything that we walked through here and you would turn it into an Ansible Playbook that can repeat the process automatically. And I'll be doing a separate video on Ansible outside of this series in the near future. So, what we did in this tutorial is we did a manual VPS, which is virtual private server, deployment. So, you get full control. You also need to understand the fundamentals and you also get a fixed monthly cost. And the reason I like covering this approach before we do a containerized deployment is because once you understand this, you also understand more about what Docker and manage platforms are abstracting away from you. So, in the next tutorial, we'll deploy the same application using a containerized cloud deployment with Google Cloud Run. So, we'll use Docker containers, managed infrastructure, and it scales to zero when there's no traffic. Both of these are valid production choices and it really depends on your needs uh which one makes more sense for your project. So, if using a VPS suits your needs, then this will basically wrap up the series for you. You've got a fully functional FastAPI app deployed out to the public with a domain name and everything like that. But, if you want to compare this deployment strategy with a Docker deployment, then feel free to continue watching this series to see how that's done. But, I think that's going to do it for this video. Hopefully now you have a good idea of how to deploy a FastAPI application to a VPS with proper security handling, Nginx as a reverse proxy, and systemd managing the application life cycle. In the next video, we'll learn about containerized cloud deployment with Docker and Google Cloud Run. But, if anyone has any questions about what we covered in this video, then feel free to ask in the comment section below and I'll do my best to answer those. And if you enjoy these tutorials and would like to support them, then there are several ways you can do that. The easiest way is to simply like the video and give it a thumbs up. Also, it's a huge help to share these videos with anyone who you think would find them useful. And if you have the means, you can contribute through Patreon or YouTube and there are links to those pages in the description section below. Be sure to subscribe for future videos and thank you all for watching.

Другие видео автора — Corey Schafer

Ctrl+V

Экстракт Знаний в Telegram

Экстракты и дистилляты из лучших YouTube-каналов — сразу после публикации.

Подписаться

Дайджест Экстрактов

Лучшие методички за неделю — каждый понедельник