Step-by-Step Guide: Deploying a Full-Stack App on AWS EC2.
Tags: aws, devops, tutorial, cloud
Published: 2024-05-01 16:03:27

## Introduction

After building an application, deploying it to a cloud service is crucial in making it accessible to your users. Amazon offers a service called Amazon Elastic Compute Cloud (EC2), which provides scalable and low-cost virtual servers in the cloud.

With EC2, you can easily customize the various specs of your virtual server, such as operating systems, CPU, memory, storage, and other features. You also control your security and network settings, which ensures that data is only accessible to authorized users. EC2 is an ideal choice for hosting applications of different sizes and complexities. 

In this guide, you will learn how to deploy a full-stack application that comprises a Typescript Next.js client and Node.js server to Amazon EC2. You will cover steps to:
 - Set up an AWS EC2 instance.
 - Install PostgreSQL and create a database.
 - Deploy your application code onto the instance. 

Additionally, this guide will cover how to configure your network and security settings for optimal performance and safety. Let’s get started.


## Prerequisites
To complete this guide, make sure you have the following requirements: 

1. An **[AWS](https://aws.amazon.com/)** Account.
2. **[Git bash](https://git-scm.com/download/win)** (Standalone Installer). If you need help installing Git Bash, check out this tutorial — **[How to install Git on Windows 10](https://www.youtube.com/watch?v=cJTXh7g-uCM)**.
3. **[PostgreSQL](https://www.enterprisedb.com/downloads/postgres-postgresql-downloads)** installed. Follow this tutorial — **[How to Install PostgreSQL 15 on Windows Postgres](https://www.youtube.com/watch?v=0n41UTkOBb0)** for guidance on how to install PostgreSQL. 
4. Some familiarity with **[PostgreSQL](https://www.postgresql.org/)** and basic **[Linux](https://www.linux.org/)** commands. However, if you don't have any prior experience, don't worry. This guide is beginner-friendly. 
    But, If you are interested in learning more about these topics, you can acquire the necessary knowledge through the following courses: 
 - **[Learn PostgreSQL Tutorial - Full Course for Beginners](https://www.youtube.com/watch?v=qw--VYLpxG4)**
 -  **[Introduction to Linux and Basic Linux Commands for Beginners](https://www.youtube.com/watch?v=IVquJh3DXUA)**.

## Step 1: Launch an EC2 instance. 
In this step, you will set up and launch an AWS EC2 Ubuntu instance. 

The Ubuntu operating system is a popular choice when setting up an AWS EC2 instance. It is open-source and comes with no licensing fees. Ubuntu supports a vast range of software and has a thriving community that makes it easier to find help.

To start, log in to your **[AWS console](https://aws.amazon.com/console/)**. Next, search and select **EC2** using the search bar.

![aws console](https://paper-attachments.dropboxusercontent.com/s_80B2CBE3538F634738DEF6AE0942AE3A8E91CCD74DB7517595EB595DA08646A4_1713627021943_11-mh.png)


Click on **Launch instance** to begin set up and launch your AWS EC2 instance.

![launch instance button ](https://paper-attachments.dropboxusercontent.com/s_80B2CBE3538F634738DEF6AE0942AE3A8E91CCD74DB7517595EB595DA08646A4_1713737293092_2024-04-22_04h05_51-mh.png)


To proceed, provide a unique and descriptive name for your instance. 

![aws ec2 instance name field](https://paper-attachments.dropboxusercontent.com/s_80B2CBE3538F634738DEF6AE0942AE3A8E91CCD74DB7517595EB595DA08646A4_1713628034805_2024-04-20_21h45_59-mh.png)


Next, in the **Application and OS Images** section, select **Ubuntu**.

![aws ec2 application and os images section](https://paper-attachments.dropboxusercontent.com/s_80B2CBE3538F634738DEF6AE0942AE3A8E91CCD74DB7517595EB595DA08646A4_1713628617098_2024-04-20_21h48_17-mh.png)


By default, the **Instance type** section is preselected for you. The default option is sufficient for this guide and will save you money. But if you want more resources and power, you can select a higher instance type, which will come at a higher cost.


![aws instance type section](https://paper-attachments.dropboxusercontent.com/s_80B2CBE3538F634738DEF6AE0942AE3A8E91CCD74DB7517595EB595DA08646A4_1713647876467_2024-04-21_03h16_42-mh.png)


In the Key pair section, click **Create new key pair** to generate a new key pair. This key pair will be used to securely connect to your AWS EC2 Ubuntu instance using SSH.

![aws key pair section](https://paper-attachments.dropboxusercontent.com/s_80B2CBE3538F634738DEF6AE0942AE3A8E91CCD74DB7517595EB595DA08646A4_1713648253158_2024-04-21_03h22_29-mh.png)


You will next be prompted to set up your Key pair. Enter a Key pair name, keep the default options, and click on **Create key pair**. 


 > _Once you click **Create key pair**, your Key pair will be downloaded automatically._

![aws ec2 key pair section prompt](https://paper-attachments.dropboxusercontent.com/s_80B2CBE3538F634738DEF6AE0942AE3A8E91CCD74DB7517595EB595DA08646A4_1713648655337_2024-04-21_03h28_03-mh+1.png)


In the **Network Settings** section, leave the first option as default. Beside the SSH checkbox, click the dropdown menu to select the location from which you want to connect to your instance.

There are three options available in the dropdown menu:

- The **0.0.0.0/0** option allows you to connect to your instance from any machine.
- The **custom** option provides greater control over who and where your instance can accept connections.
- The **My IP** option allows you to **only** connect to your instance from your local machine. 

Finally, enable the HTTP and HTTPS options to ensure your instance is accessible via the web. 


![aws network and setting section](https://paper-attachments.dropboxusercontent.com/s_80B2CBE3538F634738DEF6AE0942AE3A8E91CCD74DB7517595EB595DA08646A4_1713837911428_2024-04-23_08h03_27-mh.png)


Keep the default options for the **Configure storage** and **Advanced details** sections, as they offer the best performance for your EC2 instance. However, if you have specific requirements, you can customize them.
 

![aws configure storage and advanced details section screenshot](https://paper-attachments.dropboxusercontent.com/s_80B2CBE3538F634738DEF6AE0942AE3A8E91CCD74DB7517595EB595DA08646A4_1713734976800_2024-04-22_03h00_32-mh.png)


Click the **Launch instance** button to  launch your instance. 

![aws instance being launched screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ymw8d2s3gt9temtv383k.png) 

You have successfully launched your instance. Next, you will establish a secure SSH connection to your instance.

![successful aws instance launch screenshot](https://paper-attachments.dropboxusercontent.com/s_80B2CBE3538F634738DEF6AE0942AE3A8E91CCD74DB7517595EB595DA08646A4_1713738458120_2024-04-22_04h23_40-mh.png)

## Step 2. Connect to your AWS EC2 Ubuntu Instance
You will establish a connection to your AWS EC2 Ubuntu instance using SSH. SSH ensures secure and encrypted access to your instance. To authenticate the connection, you will use the key pair you created earlier to verify your identity and access the instance.

Navigate to your EC2 dashboard and click on **Instances (running)** in the resources tab to access your running instance(s). 

![aws ec2 dashboard screenshot](https://paper-attachments.dropboxusercontent.com/s_80B2CBE3538F634738DEF6AE0942AE3A8E91CCD74DB7517595EB595DA08646A4_1713739833048_2024-04-22_04h40_11-mh.png)

After navigating to the **Instances** page, click on the instance that you created recently.

![aws instances page screenshot](https://paper-attachments.dropboxusercontent.com/s_80B2CBE3538F634738DEF6AE0942AE3A8E91CCD74DB7517595EB595DA08646A4_1713740990362_2024-04-22_05h06_20-mh+1.png)


Next, click on the **connect** button located in the top-right corner of your screen. This will take you to the **Connect to Instance** page, where you can connect to your instance via several options. 

Select the **SSH client** tab to review your connection details. 


![aws ec2 connect to instance page screenshot](https://paper-attachments.dropboxusercontent.com/s_80B2CBE3538F634738DEF6AE0942AE3A8E91CCD74DB7517595EB595DA08646A4_1713773479931_2024-04-22_14h08_14-mh.png)


To connect to your instance, open Git Bash, navigate to the directory where your Key pair was downloaded, and execute the following commands: 

 > _Press CTRL + C to copy and CTRL + Shift + Insert to paste in Git Bash._

```Bash
chmod 400 "your-key-pair-name"
ssh -i "your-key-pair-name" ubuntu@ec2-*-*-*.eu-north-1.compute.amazonaws.com
```
After executing the command, you will get the following prompt: 

`The authenticity of host 'ec2-*-*-*-*.eu-north-1.compute.amazonaws.com (*.*.*.*)' can't be established.
ED25519 key fingerprint is SHA256:<some-text>.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])?`

Type **`yes`** and hit the **ENTER** button to proceed. 

If connected, your current directory should change to:
**`ubuntu@ip-your-ip:~$`**

You have now successfully connected to your AWS EC2 Ubuntu instance. The next step is to update and upgrade your instance to ensure you have the latest security patches and features. 

## Step 3: Update and Upgrade your instance. 
Here, you will update and upgrade your instance to ensure it is up to date with the latest updates and upgrades.

Run the following commands to update and upgrade your instance:

```Bash
sudo apt update 
sudo apt upgrade
```
When you run the **`sudo apt upgrade`** command, you may get this message:
`After this operation, 36.9 KB of additional disk space will be used. Do you want to continue?[Y/n]` 

Type **`Y`** and press the **ENTER** key to continue.


Next, you may get a prompt to upgrade the kernel. Press the **ENTER** button to proceed with the upgrade. Once you do that, a new prompt will appear where you will check specific values. 


![ec2 ubuntu instance kernel upgrade prompt screenshot](https://paper-attachments.dropboxusercontent.com/s_80B2CBE3538F634738DEF6AE0942AE3A8E91CCD74DB7517595EB595DA08646A4_1713840539666_2024-04-23_08h42_49.png)


To continue, navigate the list of services using the **ARROW** keys. Use the **SPACEBAR** key to mark each unchecked checkbox. Once all the checkboxes are marked, press **ENTER** to complete the process.


![items needed to be upgraded in your ec2 instance screenshot](https://paper-attachments.dropboxusercontent.com/s_80B2CBE3538F634738DEF6AE0942AE3A8E91CCD74DB7517595EB595DA08646A4_1713841342300_2024-04-23_08h44_15.png)


Your instance is now ready for use. Next, you will set up PostgreSQL.

## Step 4: Setting up a PostgreSQL Database.  
In this step, you will install, and set-up PostgreSQL for your application on your EC2 instance. While you could have used other databases and saved time, being able to set up a traditional, relational database like PostgreSQL in a Linux environment is a valuable skill.

It is recommended to create a database for your application in your local environment and then transfer a copy of it to your instance, rather than creating a new database on your instance. 

This approach saves time, particularly if you already have a database on your local environment. Additionally, transferring a copy of your local database ensures data accuracy, and consistency as your local database may contain a large amount of data.

Run the commands below in your local environment using the command prompt or any terminal of your choice to: 
 - connect to postgres
 - create a new database, connect to it
 - create a table
 - exit

```Bash
psql -U postgres
create database todo_app;
\c todo_app;
create table todo (id BIGSERIAL NOT NULL PRIMARY KEY, newItem VARCHAR(255) NOT NULL);
\q
```

Next, use the following command to create a backup of the newly created database in your local environment:

```Bash
pg_dump -U postgres -f todo_app.pgsql -C todo_app 
```
 
In the command above: 
 - **`pg_dump`** is a CLI tool for creating database backups.  
 - The **`-U`** flag specifies the user to connect to PostgreSQL with.
 - The **`-f`** flag specifies the output file, which in this case is **`todo_app.pgsql`**.  
 - The **`-C`** flag is used to include commands for creating the **`todo_app`** database in the backup file specified by **`-f`**. 

After running the **`pg_dump`** command, the **`todo_app.pgsql`** file will be downloaded in the current directory. Move the backup file to the directory where your AWS Key pair is. Your key pair will be needed to authenticate your connection and accept incoming files. 


You will use the **[SCP](https://en.wikipedia.org/wiki/Secure_copy_protocol)** protocol to move your backup file to the home directory of your Ubuntu instance. To do this, run the following command:

```Bash
scp -i your-key-pair-name todo_app.pgsql ubuntu@ec2-*-*-*-*.eu-north-1.compute.amazonaws.com:/home/ubuntu/
```
 
 If the transfer was successful, you should get this message: 
`todo_app.pgsql      100% 2372    11.3KB/s   00:00`                                                            

Now that you have successfully created and transferred a copy of your local database to your instance. Head back to Git Bash to set up PostgreSQL in your instance. 

 > _If you get logged out of your session, run the SSH command again to log back in._

```Bash
ssh -i "your-key-pair-name" ubuntu@ec2-*-*-*.eu-north-1.compute.amazonaws.com
```


To install PostgreSQL on your instance, use the command:

```Bash
sudo apt install postgresql postgresql-contrib -y
```

After installing PostgreSQL, a default user called **`postgres`** is created. You can view the user by running the command: 

```Bash
sudo cat /etc/passwd | grep -i postgres
```

**Output**: 
`postgres:x:115:123:PostgreSQL administrator,,,:/var/lib/postgresql:/bin/bash`

To set up PostgreSQL, switch to the **`postgres`** user:

```Bash 
sudo -i -u postgres
```

If connected, your current directory should change to: 
**`postgres@ip-your-ip:~`**


Create a new user using the command: 

```Bash 
createuser --interactive
```

You will be prompted for the following information. Enter your answers accordingly: 

 - Enter name of role to add: ubuntu 
 - Shall the new role be a superuser? n  
 - Shall the new role be allowed to create databases? y
 - Shall the new role be allowed to create more new roles? n 


Exit the `postgres` user using the command: 

```Bash
exit
```

Use the command below to create a password for the `ubuntu` user. 

```Bash 
 psql -d postgres
 ALTER USER ubuntu PASSWORD 'password';
 \q
```

> _If you are curious why you can connect to your database using **`psql -d <database>`** without specifying a user, this happens because PostgreSQL will try to connect with the same user as your logged-in user, which in this case is **ubuntu**. If the user **ubuntu** exists in Postgres, the connection will go through without being prompted for a password, as PostgreSQL uses peer connections for local connections._ 

Now, create a database and import the contents of the backup database file you created in your local environment: 

```Bash
psql -d postgres
create database todo_app;
\q 
psql todo_app < /home/ubuntu/todo_app.pgsql
```

**Output**: 
`.
.
.
ALTER TABLE`

To connect to your **`todo_app`** database, execute the command: 

```Bash
psql -d todo_app
select * from todo;
\q
```

Your PostgreSQL database is ready, you can proceed to set up your application.


## Step 4: Set up your application 

Now, you will clone the application code from **[GitHub](https://github.com/backendbro/todo-app-ubuntu.git)** and get your server and client ready for deployment.

### Server set up
 To install **[Node.js](https://nodejs.org/en)** run the command:

```Bash
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
```
> _You may be prompted by the kernel, press **ENTER** to proceed._ 

Clone the application into your Ubuntu instance: 

```Bash
cd ~
git clone https://github.com/backendbro/todo-app-ubuntu.git
```

Install the dependencies required by the server:

```Bash 
cd todo-app-ubuntu/server 
npm install 
sudo npm install -g nodemon
```

When working in a production environment, it's not recommended to run **`node server.js`** directly. This is because the Express server will shut down whenever you exit your instance. To avoid this, you can use **[PM2](https://pm2.keymetrics.io/)**. PM2 is a process manager that ensures your Express server stay up and running at all times. 

To install PM2 globally, run the command:

```Bash
sudo npm install pm2 -g
```

Before starting your server, compile your Typescript server with: 

```Bash
npm run build
```

Now, start your server using PM2 by running the command:

```Bash
cd ~
pm2 start /home/ubuntu/todo-app-ubuntu/server/dist/server.js --name todo-app
```

Next, configure PM2 to automatically restart your Express server in case of a crash or reboot using the command: 

```Bash
pm2 startup
```

**Output**: 
`[PM2] Init System found: systemd
[PM2] To setup the Startup Script, copy/paste the following command:
sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u ubuntu --hp /home/ubuntu`


To complete the setup for PM2, copy and run the startup script. 

> _Ensure that you copy the script from your instance as yours may differ. The command below is only an example to guide you on what to copy and run._

```Bash
sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u ubuntu --hp /home/ubuntu
```

Finally, save your current list of running processes:

```Bash
pm2 save
```


### Client set up

After starting the server, install the necessary dependencies and run the build command to prepare the client.

```Bash
cd ~ 
cd todo-app-ubuntu/client 
npm install
npm run build
```

In the next step, you will configure Nginx to serve your client so it can be accessible on the web.


### Step 5: Installing and Configuring Nginx

In this step, you will configure Nginx to serve your client side. You will need a domain name to complete this step. Just in case you have no money to spare, check out this video — **[Free Domain Name — Use Anywhere, No Credit Cards, NO CATCH!](https://www.youtube.com/watch?v=0bmopLboeIc)** for help on how to get a domain name free of cost. 


> You can find your Public IPv4 address on your instance dashboard to set up your domain.

To get started, execute the command below to install and enable Nginx:

```Bash
cd ~
sudo apt install nginx -y
sudo systemctl enable nginx
```

To set up an Nginx server block, navigate to the sites-available folder.

```Bash 
cd /etc/nginx/sites-available
```

When you run the **`ls`** command to list the files in the current directory, you'll find a server block named **`default`** which is responsible for handling requests that don't match any other server blocks in the **`sites-available`** directory. 

To view the default server block, open **`http://<your-ec2-instance-Public-IPv4-address>`** or **`http://<your-domain-name>`**  in your web browser. 

![nginx default web display](https://paper-attachments.dropboxusercontent.com/s_80B2CBE3538F634738DEF6AE0942AE3A8E91CCD74DB7517595EB595DA08646A4_1713972132515_2024-04-24_21h19_05-mh+1.png)


You can use the **`default`** server block as a template to create your server block. To do this, run the following command: 

```Bash 
sudo cp default todo_app
```

Open the **`todo_app`** server block for configuration: 

```Bash 
sudo vi todo_app
```

To begin configuring the **`todo_app`** server block, press the **`I`** key on your keyboard to enter **INSERT** mode. This will enable you to type in the required changes. Replace the contents in the first **`server`** block with the following: 

```Nginx
server {
      listen 80;
      listen [::]:80;
    
     root /var/www/html;
    
      # Add index.php to the list if you are using PHP
     index index.html;
    
     server_name your-ec2-public-ipv4-adddress your-dmain-name.com;
    
     location / {
          try_files $uri $uri/ =404;
    }
    
   
   location /api/v1/todo {
            proxy_pass http://localhost:4000;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_cache_bypass $http_upgrade;
        }

}

```

Once you have made the changes, press the **`esc`** key to exit **INSERT** mode. Next, type **`:wq`** and press **ENTER** to exit the vim terminal.

 > _Make sure to memorize the sequence for inserting values and exiting in vim, as you will need it later._

Now, enable the new server block by running the command: 

```Bash 
sudo ln -s /etc/nginx/sites-available/todo_app /etc/nginx/sites-enabled/
sudo systemctl restart nginx
```

Use the command below to move your client build files to the **`root`** directory specified in the **`todo_app`** server block:

```Bash
sudo mv /home/ubuntu/todo-app-ubuntu/client/dist/* /var/www/html
```

Now, you should be able to access your website using **`http://<your-Public-IPv4-address>`** and **`http://<your-domain-name>`**. 

But, at the moment you won’t be able to make request to the Express server, because the Express server cannot access its environment variables. In the next step, you will set up your environment variable.


## Step 6: Setting up environment variables 

In this step, you will configure your environment to allow your Express server to access its PORT number and the PostgreSQL database.

Create a **`.env`** file: 

```Bash 
cd ~
sudo vi .env
```

Copy and paste in the following environment variables into the **`.env`** file

```Bash 
PORT=4000
DB_USER=ubuntu
DB_HOST=localhost
DB_PASSWORD=password
DB_DATABASE=todo_app
DB_PORT=5432
NODE_ENV=production
```

 > _If you must enter a different value in the **`.env`** file, ensure there are no space(s) between the variable name, equal sign and your variable value._

Moving on, open the **`.profile`** file

```Bash
sudo vi /home/ubuntu/.profile
```

Enter the following command below into the **`.profile`** file to ensure your environment variables is set at all times, even when your instance reboot or crash. 

 > Make sure to enter the command at the bottom of the **`.profile`** file

```Bash
set -o allexport; source /home/ubuntu/.env; set +o allexport
```

**Visual illustration:**

![.profile illustration](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sw3n62sp7ikw75jmyw12.png)

For this change to take effect, restart your instance:

```Bash
sudo reboot
```

You will be logged out from your Ubuntu instance. Re-connect to move on to the next step. 

After setting up your environment variables, you will enable a firewall to filter specific connections.


## Step 7: Enable firewall

In this step, you will enable the firewall to filter the type of requests that can reach your Ubuntu instance. 

To allow SSH, HTTP, and HTTPS requests, run the commands:

```Bash
sudo ufw allow ssh
sudo ufw allow http
sudo ufw allow https
```

To apply your changes, use:

```Bash 
sudo ufw enable
```

After running the **`sudo ufw enable`** command you will receive this message: 
`Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
`
Type **`y`** and press **ENTER**. 

After filtering specific requests, the next step is to enable SSL. This will ensure your website is secure, and visitors can access it without encountering pesky security warnings on their browsers. With SSL, your site will be safe and trustworthy.


## Step 8: Enable SSL 

In this step, you will generate an SSL certificate with **[Certbot](https://certbot.eff.org/)** and configure **[Nginx](https://www.nginx.com/)** to redirect HTTP traffic to HTTPS.

Install Certbot by running the command: 

```Bash
sudo snap install --classic certbot
```

Enable certbot using: 

```Bash
sudo ln -s /snap/bin/certbot /usr/bin/certbot
```

Configure Cerbot for Nginx, with the command: 

```Bash 
sudo certbot --nginx
```

When running the certbot configure command, you will be prompted for the following. Here's how you should answer:

 - Enter email address ... ? valid-email@email.com 
 - Please read the Terms of Service at ... ? Y
 - Would you be willing, once your first certificate is successfully issued... ? N
 - Which names would you like to activate HTTPS for? ... ? 1

Your SSL certificate has been successfully set up. Now, head over to your website to view it.


## Conclusion. 

By  following this guide, you deployed a full-stack Next.js and Node.js application to an AWS EC2 Ubuntu instance. However, It is important to note that in larger applications, you might need to take a different approach where you may have to split your application services into two or more instances.

In this guide, you covered the fundamental steps involved in the process. Firstly, you learned how to set up and configure an AWS instance. Next, you learned how to set up PostgreSQL on your AWS EC2 Ubuntu instance. You created a new Postgres user, and a new database. You also learned how to set up and configure Nginx and PM2 as a reverse proxy and process manager, respectively. In addition, you learned how to create an SSL certificate, which is crucial for secure and encrypted communication between your application and the user's web browser. 

Although some details may vary depending on your specific application, this guide provides a solid foundation for deploying an application on AWS EC2. With this knowledge, you can confidently deploy your application on AWS and take advantage of its scalability, reliability, and security. Happy deployment 😊