Skip to content

Instantly share code, notes, and snippets.

@cgsmith
Last active May 16, 2024 13:06
Show Gist options
  • Save cgsmith/8797709e2fa6edbb3113e16ec8a96de3 to your computer and use it in GitHub Desktop.
Save cgsmith/8797709e2fa6edbb3113e16ec8a96de3 to your computer and use it in GitHub Desktop.
Deployed Laravel App with Bitbucket Pipelines

Laravel + Bitbucket Pipeline Deployment

This is the process I use for deploying a Laravel application to Bitbucket. Once I setup the Debian server with Caddy and PHP-FPM it is usually ready to go.

Debian preparedness

  1. Install Caddy
  2. Install PHP-FPM
  3. mkdir -p /var/builds/staging.example.com && mkdir -p /var/www
  4. Create .env and storage folder in /var/builds/staging.example.com
    1. The deploy.sh script will symlink to the builds folder
  5. Configure Caddy and test configuration

Once that is done you can configure your Bitbucket pipeline. I will make a YouTube on the overall process and post it here.

Bitbucket Pipelines for Continuous Deployment

Pipelines runs on pushes to master. The pushes are done through a pull request and executes the commmands in ./bitbucket-pipelines.yml. You can see the pipeline status on bitbucket.org. The Bitbucket Repository will require the following variables setup to work properly. These are located under Repository Settings -> Repository Variables

Repository Variable Setup in Bitbucket Description
USER ubuntu Bitbucket uses this user to execute SSH and SCP commands.
BUILD_PATH staging.example.com The relative path to drop the ZIP file in /var/builds/$BUILD_PATH which is defined in bitbucket-pipelines.yml
SERVER staging.example.com Server Bitbucket will use for sending ZIP file to and executing commands
SSH_PORT 2222 Port used for SCP and SSH commands
SSH_DEBUG false Debug the SSH connection by setting to true

Process for the Pipeline

  1. The pipeline builds the project with npm and composer.
  2. Composer tests are run in the docker container the pipeline has build
  3. Successful builds create two artifacts. One build.zip and the deploy.sh file.
  4. Bitbucket uploads the build.zip and deploy.sh to the BUILD_PATH on the remote SERVER specified.
  5. Bitbucket then calls deploy.sh with the appropriate SHA and branch and your app is deployed! 🚀🚀🚀

deploy.sh

deploy.sh is a script that is used for the atomic deployments. An atomic deployment simply changes the symlink for the webserver and then restarts the webserver after running any database migrations. This process, like all processes, can always be improved upon. An atomic deployment allows a server administrator to symlink to a prior version of working code as long as they navigate to the correct git SHA and change the symlink. In the future the deploy.sh script could probably perform a database backup before a migration is applied.

Troubleshooting pipeline issues locally

If the pipeline fails you can test locally with Docker. Bitbucket has decent documentation for doing this. They also have a link that discusses "Troubleshooting locally with Docker". Below is an example of how I was able to troubleshoot the pipeline issues.

docker build --memory=1g --memory-swap=1g -t chris/bbtermin:tag -f my.dockerfile .
docker run --network=host --name bbmysql -e MYSQL_DATABASE='laravel' -e MYSQL_ROOT_PASSWORD='password' -e MYSQL_ROOT_HOST='%' -e MYSQL_USER='sail' -e MYSQL_PASSWORD='password' -d mysql:8.0
docker run -it --network=host --memory=4g --memory-swap=4g --memory-swappiness=0 --cpus=4 --entrypoint=/bin/bash chris/bbtermin:tag

If you receive an error about the container being used for mysql - you can docker rm container-name and restart.

You can also ignore any error about the kernel not supporting memory swappiness

image: cgsmith105/php:8.3
pipelines:
branches:
master:
- step:
services:
- mysql
name: Test application
script:
- curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
- export NVM_DIR="$HOME/.nvm"
- '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"'
- nvm install 20
- npm install
- npm run build
- export COMPOSER_ALLOW_SUPERUSER=1
- curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
- composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
- php -r "file_exists('.env') || copy('.env.example', '.env');"
- php artisan key:generate
- chmod -R 777 storage bootstrap/cache
- php artisan migrate:fresh --seed
- php artisan serve &
- composer test
- composer install --no-dev -o --no-scripts --no-ansi --no-progress -q --prefer-dist
- rm .env #remove env variable before build
- zip -q -r build.zip . -x \*.git\* \*tests/\* \*bootstrap/cache/\* \*storage/\* \*node_modules/\**
caches:
- composer
artifacts:
- build.zip
- deploy.sh
- step:
name: Deploy build with SCP to staging.termin.mount7.com
script:
- pipe: atlassian/scp-deploy:1.5.0
variables:
USER: $USER
SERVER: $SERVER
REMOTE_PATH: '/var/builds/$BUILD_PATH/build-$BITBUCKET_COMMIT.zip'
LOCAL_PATH: 'build.zip'
EXTRA_ARGS: ["-P", $SSH_PORT]
DEBUG: $SSH_DEBUG
- step:
name: Install deploy.sh artifact
script:
- pipe: atlassian/scp-deploy:1.5.0
variables:
USER: $USER
SERVER: $SERVER
REMOTE_PATH: '/var/builds/$BUILD_PATH/deploy.sh'
LOCAL_PATH: 'deploy.sh'
EXTRA_ARGS: ["-P", $SSH_PORT]
DEBUG: $SSH_DEBUG
- step:
name: Set permissions for deploy.sh
script:
- pipe: atlassian/ssh-run:0.8.0
variables:
SSH_USER: $USER
SERVER: $SERVER
COMMAND: 'chmod +x /var/builds/$BUILD_PATH/deploy.sh'
PORT: $SSH_PORT
DEBUG: $SSH_DEBUG
- step:
name: Move current app to OLD and replace with new BUILD ZIP
script:
- pipe: atlassian/ssh-run:0.8.0
variables:
SSH_USER: $USER
SERVER: $SERVER
COMMAND: '/var/builds/$BUILD_PATH/deploy.sh build-$BITBUCKET_COMMIT build-$BITBUCKET_COMMIT.zip $BUILD_PATH $BITBUCKET_BRANCH'
PORT: $SSH_PORT
DEBUG: $SSH_DEBUG
definitions:
services:
mysql:
image: mysql:8.0
variables:
MYSQL_ROOT_PASSWORD: 'password'
MYSQL_ROOT_HOST: '%'
MYSQL_DATABASE: 'laravel'
MYSQL_USER: 'sail'
MYSQL_PASSWORD: 'password'
#!/bin/bash
# located in /var/builds/$3/deploy.sh
# deploy.sh build-$BITBUCKET_COMMIT build-$BITBUCKET_COMMIT.zip $BUILD_PATH $BITBUCKET_BRANCH'
# $1 commit to unzip
# $2 zip file to unzip
# $3 Path to unzip to
# $4 branch path
echo "Unzipping build $2"
echo "Deploying for $4"
unzip -q -o -d /var/builds/$3/$1/ /var/builds/$3/$2 || { echo 'unzip failed on build' ; exit 1; }
ln -sf /var/builds/$3/$1 /var/www/$3
# Link .env to www folder
ln -sf /var/builds/$3/.env /var/www/$3/.env
ln -sf /var/builds/$3/storage/app/public /var/www/$3/storage/app/
chown -R www-data:www-data /var/builds/$3
chmod -R 777 /var/builds/$3/$1/storage /var/builds/$3/$1/bootstrap/cache
# Update SQL
php /var/www/$3/artisan migrate --no-interaction --force || { echo 'failed to update SQL' ; exit 1; }
# remove zip file
rm /var/builds/$3/$2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment