Deploying and hosting Full-Stack Laravel apps
Tags: webdev, laravel, devjournal
Published: 2022-12-05 21:04:55

In today's development world is easier than ever to deploy your projects online with minimal configuration, with frontend and JAM Stack/node powered apps there are a lot of good options: Vercel, Netlify, GitHub Pages, Gitlab Pages, Firebase Hosting. 

Unfortunately for modern php/Laravel powered apps with Vue/React frontend there's no too many with minimal configuration with the quality of the options mentioned above out of [Heroku](https://www.heroku.com/) before the change of its terms and I don't find a good one-to-one replacement for it.

Here I want to share my journey replicate a workflow close to the Heroku experience with less than 10 dollars/month.

## The needs

I am talking about a semi-production ready app so what are our checklist

- Separated `landing page` from the app. we don't want app errors affecting our marketing / guides pages neither slowing them down and want to implement SEO that probably our app itself doesn't need, because the routes are guarded.

- The `Main app` with Laravel/Jetstream and all resources we need for it (Database, cron tabs, redis)

- A `Database Manager` to quick review the database if we are far from our computer and we need to manage the data online

- Ownership, we don't need to pay extra money just to have a basic functionality we would like (now everything is a plugin, DB, Crons, etc...)

- Build our frontend and deploy to the server when mergin our branch with master like Heroku and Vercel-like services do it.

> For production app with > 100 paying customers maybe is better option to migrate to something like [the Laravel services](https://forge.laravel.com/) but for side projects maybe we just need to deploy and to show the app to your friends or get some users and see how it goes spending less as possible.

## The solution (first attempt) 

In my first attempt I used docker-compose to set up the environment with the services I needed here the most difficult part was serve the landing and app through Nginx as load balancer and its ssl but I found tools that make the task easier.

- Cloud provider: Digital Ocean
- Apps: Docker, Docker Compose
- Tools: [ssl-companion](jrcs/letsencrypt-nginx-proxy-companion), jwilder/nginx-proxy

### Steps
**On the server:**
- Start a doplet with Digital Ocean (you have to give access to your ssh key when creating).
- Set up your domains in DO.
- Install docker
- Setup a [git bare repository](https://www.digitalocean.com/community/tutorials/how-to-set-up-automatic-deployment-with-git-with-a-vps) on your server to accept git push from your local as you push to Github (later I automized this step with a Laravel package [insane/remotr]() and just need to set and set the `REMOTR_SERVER_IP` and `REMOTR_REPO_NAME` in your `.env` doc.).

**On local:**
- Verify that you can connect to your server via SSH
- Setup a docker-compose.yml for your Laravel project here was mine [Loger's docker-compose](https://github.com/jesusantguerrero/atmosphere/blob/master/docker-compose.yml).
- Add your server URL to git remote.
- git push
- Run your `docker compose up` command on the server via SSH and that's it

Maybe there's a shorter way to doit but not at USD 6.00 / Month without cold starts or using other services.

## My current solution

Docker Compose with php and Laravel wasn't a good option for a $6 Doplet it was slower than my local environment out of the box so I ended up installing the [LEMP stack](https://www.digitalocean.com/community/tutorials/how-to-install-linux-nginx-mysql-php-lemp-stack-on-ubuntu-20-04).

One pain point I faced with the first approach was having to build the assets on my machine or the server it would be easier to manage this with GitHub actions so, I set it up.

Later I found [a tutorial to connect Digital Ocean and GitHub actions](https://zellwk.com/blog/github-actions-deploy/) I just needed the SSH key to push from GitHub Action instead of my local as the first approach. So, in every merge to master if the tests are green and after the archives of my frontend code is built a git push to the server is executed. 


```yaml
 
  name: Laravel App Deployment

  on:
    push:
      branches: [ "master" ]
    pull_request:
      branches: [ "master" ]

  jobs:
    build:
    deploy:
      if: ${{ github.event_name == 'push' }}
      name: Deploy
      runs-on: ubuntu-latest
      needs: build
      environment:
        name: demo
        url: ${{ secrets.SITE_URL }}
      steps:
        - uses: actions/checkout@v3
          with:
            fetch-depth: 0
        - name: Install SSH Key
          uses: shimataro/ssh-key-action@v2
          with:
            key: ${{ secrets.DO_DOPLET_SECRET }}
            known_hosts: unnecessary

       - name: Adding Known Hosts
         run: ssh-keyscan -H ${{ secrets.SSH_HOST }}  >> ~/.ssh/known_hosts

       - name: Download frontend build
         uses: actions/download-artifact@v3
         with:
          name: frontend-build
          path: public

       - name: Store live name
         run: git remote add live ${{ secrets.DO_REPO_URL }}

       - name: Deploy with git
         env:
           branch_name: live_digital_ocean
         run: |
           echo "Deploy to staging server"
           git config --global user.email "<>"
           git config --global user.name "Action Bot"
           git add .
           git checkout -b $branch_name
           git commit -m "deploy: build"
           git push live $branch_name:master -f
           echo "deployed to prod-staging"

       - name: Update Composer PHP
         uses: appleboy/ssh-action@master
         with:
           host: ${{ secrets.SSH_HOST }}
           username: ${{ secrets.SSH_USER }}
           key: ${{ secrets.DO_DOPLET_SECRET }}
         script: |
            cd /var/www/loger.com
            composer update --ignore-platform-reqs
```


> you can check the complete file here: https://github.com/jesusantguerrero/atmosphere/blob/master/.github/workflows/laravel.yml

Updated stack:

- IaaS: Digital Ocean Doplet
- Apps: NGINX, MariaDB, PHP 8.1
- Tools: cerbot, GitHub Actions

### Steps

**On the server:**
- Start a Doplet with Digital Ocean (you have to give access to your SSH key when creating).
- Set up your domains in DO.
- Install docker
- Setup a [git bare repository](https://www.digitalocean.com/community/tutorials/how-to-set-up-automatic-deployment-with-git-with-a-vps) on your server to accept git push from your local as you push to GitHub (later I automized this step with a Laravel package [insane/remotr]() and just need to set and set the `REMOTR_SERVER_IP` and `REMOTR_REPO_NAME` in your `.env` doc.).

**On GitHub:**
- setup the SSH secrets that we need for the action:
  `SITE_URL`
  `DO_REPO_URL`
  `SSH_HOST`
  `SSH_USER`
  `DO_DOPLET_SECRET`
   
- Define our action [Loger's deploy action](https://github.com/jesusantguerrero/atmosphere/blob/master/.github/workflows/laravel.yml)

Honorable mentions:
- fly.io
- Digital Ocean App

Wrapping up

To summarize you can replicate the experience with a low cost using:

- Digital Ocean Doplet
- Create a git bare repository to push your changes with git
- Using Docker or LEMP Stack on the server
- Automate testing, build and deployment via SSH with GitHub actions

Thank you for reading, if you have any questions or want to share a stack the comments are open, as well as my [Twitter](https://twitter.com/JesusntGuerrero) and [Github](https://github.com/jesusantguerrero).