Skip to content

Instantly share code, notes, and snippets.

@eliyas5044
Last active February 2, 2024 17:10
Show Gist options
  • Save eliyas5044/c137329e800ef4761d8209dd356b5410 to your computer and use it in GitHub Desktop.
Save eliyas5044/c137329e800ef4761d8209dd356b5410 to your computer and use it in GitHub Desktop.
Github action to deploy Nuxt.js project into Cloud server.
# Deploy to aws
name: Deploy to AWS
# Controls when the action will run.
on:
push:
branches: [main]
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
node: [14]
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
- name: Checkout 🛎
uses: actions/checkout@master
- name: Setup node env 🏗
uses: actions/[email protected]
with:
node-version: ${{ matrix.node }}
check-latest: true
- name: Get yarn cache directory path 🛠
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Cache node_modules 📦
uses: actions/[email protected]
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies 👨🏻‍💻
run: yarn install --prefer-offline --frozen-lockfile --non-interactive --production=false
- name: Make envfile
uses: SpicyPizza/[email protected]
with:
envkey_APP_NAME: ${{ secrets.APP_NAME }}
envkey_APP_SHORT_NAME: ${{ secrets.APP_SHORT_NAME }}
envkey_APP_URL: ${{ secrets.APP_URL }}
envkey_API_BASE_URL: ${{ secrets.API_BASE_URL }}
envkey_AWS_CDN: ${{ secrets.AWS_CDN }}
envkey_GOOGLE_MAP_KEY: ${{ secrets.GOOGLE_MAP_KEY }}
- name: Run builder
run: yarn build
- name: Install production dependencies
run: |
rm -rf node_modules
NODE_ENV=production yarn install --prefer-offline --pure-lockfile --non-interactive --production=true
- name: Archive necessary folders and files
uses: montudor/action-zip@v1
with:
args: zip -qq -r dist.zip .env .nuxt static nuxt.config.js node_modules package.json ecosystem.config.js
# Configure AWS credential and region environment variables for use with the AWS CLI and AWS SDKs
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Upload to Amazon S3
run: aws s3 sync --delete .nuxt/dist/client s3://${{ secrets.AWS_S3_DESTINATION }}
- name: Install SSH key
uses: shimataro/ssh-key-action@v2
with:
key: ${{ secrets.SSH_KEY }}
known_hosts: ${{ secrets.HOST }}
- name: Adding Known Hosts
run: ssh-keyscan -H ${{ secrets.HOST }} >> ~/.ssh/known_hosts
- name: Rsync over ssh
run: rsync -avz dist.zip ${{ secrets.USERNAME }}@${{ secrets.DEMO_HOST }}:/home/${{ secrets.USERNAME }}
- name: Executing remote ssh commands using ssh key
uses: appleboy/ssh-action@master
env:
SHA: ${{ github.sha }}
USERNAME: ${{ secrets.USERNAME }}
APP_NAME: ${{ secrets.APP_SHORT_NAME }}
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_KEY }}
envs: USERNAME,APP_NAME
script: |
export PATH=/home/ubuntu/.fnm:$PATH
eval "`fnm env`"
source ~/.bashrc
rm -rf app
mkdir app
mv dist.zip app
cd app
unzip dist.zip
pm2 delete DemoApp
pm2 start --only DemoApp

HTTPS config

map $sent_http_content_type $expires {
  "text/html" epoch;
  "text/html; charset=utf-8" epoch;
  default off;
}

server {
  listen 80;
  listen 443 ssl http2;
  server_name example.com;

  if ( $scheme = "http" ) {
    return 301 https://$host$request_uri;
  }

  # SSL
  ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

  # security headers
  # add_header X-Robots-Tag "noindex, nofollow";
  add_header X-XSS-Protection "1; mode=block" always;
  add_header X-Content-Type-Options "nosniff" always;
  add_header Referrer-Policy "no-referrer-when-downgrade" always;
  #add_header Content-Security-Policy "default-src 'self' http: https: ws: wss: data: blob: 'unsafe-inline'; frame-ancestors 'self';" always;
  add_header Permissions-Policy "interest-cohort=()" always;
  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

  location /api/ {
    expires off;

    proxy_pass http://127.0.0.1:3000/api/;
    proxy_cache off;
  }

  proxy_redirect off;
  proxy_http_version 1.1;
  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;
  proxy_set_header "Connection" "";
  # Proxy timeout
  proxy_send_timeout 1m;
  proxy_read_timeout 1m;
  proxy_connect_timeout 1m;

  location / {
    expires $expires;

    proxy_pass http://127.0.0.1:3000/;
  }

  access_log off;
  error_log /var/log/nginx/example.com-error.log error;

  # ACME-challenge
  location ^~ /.well-known/acme-challenge/ {
    root /var/www/_letsencrypt;
  }

  location ~ /\.(?!well-known).* {
    deny all;
  }
}

Github Secrets

  • We need to add these as key, value pair in Github Secrets

These for .env file

APP_NAME="My Demo App"
APP_SHORT_NAME="MyDemoApp"
APP_URL=https://my-demo-app.com
API_BASE_URL=https://api.my-demo-app.com
AWS_CDN=https://cdn.my-demo-app.com
GOOGLE_MAP_KEY=some_random_string

These for AWS configure

AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_REGION=
AWS_S3_DESTINATION="bucket/folder"

These for Server

# SSH private key
SSH_KEY=
# Server host address
HOST=
# Server username
USERNAME=

Server Setup

  1. SSH into server
  2. Update server dependencies
  3. Generate RSA ssh key
  4. Apped public key into authorized_keys
  5. Add private key into github secrets
  6. Install unzip package
sudo apt install unzip
  1. Install fnm node manager
curl -fsSL https://fnm.vercel.app/install | bash
source /home/ubuntu/.bashrc
  1. Install pm2
npm install -g pm2
  • Init System pm2 Service
pm2 startup

To setup the Startup Script, copy/paste the following command 9. Install nginx

sudo apt install nginx
  1. Configure nginx config file
cd /etc/nginx/sites-available
sudo nano my-demo-app.com.conf
  • Paste the bellow config file as example
map $sent_http_content_type $expires {
    "text/html"                 epoch;
    "text/html; charset=utf-8"  epoch;
    default                     off;
}

server {
    listen          80;             # the port nginx is listening on
    server_name     my-demo-app.com;    # setup your domain here

    gzip            on;
    gzip_types      text/plain application/xml text/css application/javascript;
    gzip_min_length 1000;

    location / {
        expires $expires;

        proxy_redirect                      off;
        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;
        proxy_read_timeout          1m;
        proxy_connect_timeout       1m;
        proxy_pass                          http://127.0.0.1:3000;
    }
}
  • Save the file with Command + O or Ctrl + O and exit by pressing X keyboard
  • Create symbolic link into nginx sites-enabled folder
sudo ln -s /etc/nginx/sites-available/my-demo-app.com.conf /etc/nginx/sites-enabled/
  • Check the nginx by sudo nginx -t
  • If nginx config is OK, then reload nginx by sudo service nginx reload
  1. Install Let's Encrypt to add SSL certificate
cd ~
sudo apt update
sudo apt install certbot python3-certbot-nginx
  • Get SSL certificate
sudo certbot --nginx -d my-demo-app.com
or
sudo certbot certonly --standalone --preferred-challenges http -d my-demo-app.com
  1. Verifying Certbot Auto-Renewal
sudo systemctl status certbot.timer
sudo certbot renew --dry-run

Step 1: Generate a SSH Key

  1. SSH into server
ssh user@host
// or
ssh -i ~/.ssh/ubuntu.pem ubuntu@server_ip_address
  1. Update server dependencies
sudo apt update
sudo apt upgrade -y
  1. Generate RSA ssh key
ssh-keygen -t rsa -b 4096 -C "[email protected]"
  • This creates a new SSH key, using the provided email as a label.
  • When you're prompted to "Enter a file in which to save the key," press Enter. This accepts the default file location.
  • You’ll also be asked to provide a passphrase. Leave this empty by pressing Enter since we can’t enter passwords when Github Actions run the SSH command for us.

Step 2: Adding the Public Key into authorized_keys

We need to add the public key (id_rsa.pub) to authorized_keys so machines using the private key (id_rsa) can access the server.

cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys

Step 3: Adding the private key to your repository’s secrets

  • Go to your repository on Github and click on Settings, then Secrets, then Actions. You should see a button that says New repository secret.
  • Write Name, here we'll use SSH_KEY
  • Write Value, to get value, we need to go back to server and copy private key.
cat ~/.ssh/id_rsa
  • Copy the whole key starting from -----BEGIN OPENSSH PRIVATE KEY----- and ending at -----END OPENSSH PRIVATE KEY----- and paste in Value and click Add secret button.

Step 4: Adding the Private key to a Github Actions Workflow

  • Add HOST secret in Github, here HOST=IP_ADDRESS_OF_HOST
- name: Install SSH key
  uses: shimataro/ssh-key-action@v2
  with:
    key: ${{ secrets.SSH_KEY }}
    known_hosts: ${{ secrets.HOST }}
  • We can generate correct know_hosts using ssh-keyscan command like ssh-keyscan -H IP_ADDRESS_OF_HOST
  • Let's add this in Github action
- name: Adding Known Hosts
  run: ssh-keyscan -H ${{ secrets.HOST }} >> ~/.ssh/known_hosts

Step 5: Rsync into Server

We can finally rsync via SSH into the server. Github action is like this - here HOST = Server IP address and USERNAME = Server username

- name: Rsync over ssh
  run: rsync -avz dist.zip ${{ secrets.USERNAME }}@${{ secrets.HOST }}:/home/${{ secrets.USERNAME }}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment