This is a simple guide towards deploying multiple applications on a single virtual machine instance, like EC2 on AWS. For the sake of a running example, I will be talking about a Django application that I deployed for developmental / testing and production pipeline. This is not something that is set in stone, there could be more novel approaches to this, however, this is something that worked for me. The steps will remain similar for any other application, it will be indicated where you might have to change according to your own requirements. Though, I feel because of all these AI tools coming in, my blog will prove to be futile in the longer run. However, it’s merely for cataloguing purposes so that I can always refer back to the processes to check if I have missed out on anything if I ever intend to do something similar in the future
Code pipeline
Assuming that you are using GitHub to host your source code, I am providing the steps to setup the code pulling pipeline. Even with GitHub, there may be some different ways to execute this step, I am using a manual pull approach as I didn’t have access to GitHub actions or any other tool at the time.
To generate a valid SSH key for authenticating with GitHub, use the following command:
ssh-keygen -t ed25519 -C “your@email.com“
Make sure you replace “your@email.com”
with your registered email with GitHub. Enter a password for safety and save it somewhere in case you forget it. Now, you shall copy the key and provide it to GitHub so it can register it as a valid key. Copy and paste the output of the following code:
cat .ssh/id_ed25519.pub
This is based on the assumption that you’re in the user
directory. So, essentially, the .ssh
folder is supposed to be in the “home” or “user” directory. The output is supposed to look something like this:
ssh-ed25519 ADDDC3NzaD1lZCI1NTE5VAZAICmtA0p1hPVqKutVwCvCn8j24TZcSn2tj5IuXIDLLLna your@email.com
Now you can go ahead, create separate folders and clone your repositories. For my project, I created folders name dev
and prod
for developmental and production pipeline respectively. I clone the repository and branched to respective branches.
You might have to run the following commands in case it is a fresh VM that you setup and doesn’t have the necessary libraries / software installed.
sudo apt update && sudo apt upgrade
sudo apt install python3.12-venv nginx certbot python3-certbot-nginx -y
These commands will install the venv
module for Python v3.12. Make sure you install for the version available on your machine.
Once the repositories are setup, you’ve installed the packages and setup virtual environments and what not, it’s time to start deploying. A good idea would be to use some preliminary commands to test that your application is running as expected. I ran the following commands:
python manage.py runserver
The application was running fine, after I had provided the correct .env
file for the environment variables.
nginx proxy
To deploy applications at scale, you will have to keep in mind security and accessibility for the same. I wanted to deploy on SSL using subdomains. For the sake of example, let’s say backend.example.com
was used for production, and dev.backend.example.com
was used for development. Assuming you are able to make things work for backend.example.com
, you can alternatively use devbackend.example.com
as well, but I was feeling adventurous so I added more depth to the subdomains.
If you ran the previous commands, it is assumed nginx
is installed. Now write the following to create the proxies.
sudo nano /etc/nginx/sites-available/backend.example.com
Obviously, replace backend.example.com
with your actual domain / subdomain name.
server {
listen 80;
server_name backend.example.com;
location / {
proxy_pass http://localhost:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
This will allow you to serve the app that is running locally on the VM. Here, it is assumed that the app is running on http://localhost:5000
. Make sure you change the value of the port accordingly.
Once you have updated that file, save it and run the following commands to check if all is well.
sudo nginx -t
sudo systemctl reload nginx
Typically it should work just fine without errors. You might find yourself stumbling across some syntax error, it’s nothing that you can’t fix on your own. Trust me.
You should repeat the same steps for your other domains, i.e. dev.backend.example.com
or devbackend.example.com
or whatever.example.com
Lastly, we need to create a symbolic link to sites-enabled
after having created the said file. Run the following command for the same:
sudo ln -s /etc/nginx/sites-available/backend.example.com /etc/nginx/sites-enabled/
After all this, don’t forget to point the domain to your VM. You can do so by adding an A
record to your DNS for the domain for the given subdomain (backend
in this case). The value should be the public IP address of the VM. Make sure it is the public IP address, and if you are using AWS, make sure you’ve linked an elastic IP address. This comes in handy when you have to restart your VM instance for whatever reason. It saves you the trouble of updating the IP addresses in the DNS, because without the elastic IP address, the instance is assigned a different public IP.
Daemon
Daemons are programs which run in the background. Since, we want the application to persist and run even when the SSH session has been terminated, we shall write a small script to do so. Run this command:
sudo /etc/systemd/system/prodbackend.service
This particular script is written assuming that your app is running for the production pipeline (backend.example.com
).
[Unit]
Description=Production backend on port 5000
After=network.target
[Service]
User=root
Group=www-data
WorkingDirectory=/home/ubuntu/path/to/your/backend/folder/
ExecStart=/home/ubuntu/path/to/your/backend/folder/.venv/bin/gunicorn -- workers 3 -- bind 0.0.0.0:5000 myApplication.wsgi: application
Restart=always
[Install]
WantedBy=multi-user. target
It maybe a lot to absorb, but it’s easy when you read through it a couple of times. Firstly, update the /home/ubuntu/path/to/your/backend/folder/
to your actual path to the backend folder. Secondly, I am using .venv
as my virtual environment folder, you may have set it up differently / named it differently, make sure you add the path to the gunicorn
or uvicorn
executable binary file (assuming your application uses it). Lastly, make sure you are binding the application at the correct port. It should match with what you had provided to nginx
for that particular domain.
After making these necessary edits, save the file and run the following commands:
sudo systemctl reload systemd
sudo systemctl daemon-reload
sudo systemctl start prodbackend
sudo systemctl status prodbackend
Make sure that you’ve written the same name as the argument as you did while creating the daemon file.
You can periodically run the sudo systemctl status prodbackend
to check the state of your application, i.e. see if it is crashing or needs a restart, or check the most recent logs.
Repeat the same steps for the other domain / subdomain.
SSL
Something that drives everyone crazy at some point if not done correctly. Make sure you’ve added the following DNS records:
CAA "0 issue letsencrypt.org" for @ or subdomain
CAA "0 issuewild letsencrypt.org" for @ or subdomain
This is something you might have to check with your DNS provider, but in general you need 2 records for your subdomains / apex domain, one with the value "0 issue letsencrypt.org"
and another with "0 issuewild letsencrypt.org"
.
Next steps are easier on the mind. Just run the following:
sudo certbot --nginx -d backend.example.com
Just follow the steps that appear on your console. If everything is alright, then it shall deploy successfully. You can verify the same by opening the /etc/nginx/sites-available/backend.example.com
file. It shall have another clause mentioning details about the SSL and redirecting to the port 443, which is used for SSL connections.
You shall repeat the same steps for your other domains / subdomains.
Conclusion
That’s it. Happy to help if you’re stuck at some point. I always recommend learning on your own, experimenting with things (safely), that’s where real education lies. However, nothing beats some good guidance when you need it. Don’t be afraid to ask.