
Deploying Laravel & Vue with GitLab CI/CD to Shared Hosting
Deploying a modern web application, especially one with a separate frontend and backend like Laravel and Vue.js, can be a complex process. While setting up a GitLab CI/CD pipeline promises automation, it often leads to a common, frustrating problem: the pipeline succeeds, but the live website shows a blank page with missing data and broken images.
Recently, while working on a project, I went through this exact scenario. The GitLab job log showed "Job succeeded,"
but the deployed site was non-functional. This led to a deep dive into server-side configurations, permissions, and application caches—issues that occur after the pipeline has successfully delivered the files.
In this post, I'll document the complete, battle-tested solution. We will build a secure GitLab CI/CD pipeline from scratch and, more importantly, cover the essential server-side commands required to bring your application to life post-deployment. This guide will serve as a definitive checklist for a successful, automated deployment.
The Goal: A Secure, "Hands-Off" Deployment
Our primary goal is to create a pipeline that automates the deployment of code and assets without ever touching the production .env
file on the server. This principle is crucial for security. The application code lives in Git, but the server's secrets (database passwords, API keys) live only on the server.
1. Configure GitLab CI/CD Variables
This is the most critical step. All sensitive information must be stored as CI/CD variables in your GitLab project. Navigate to:
Settings > CI/CD
→ expand the Variables section.
Deployment & Server Credentials
Key | Value | Notes |
---|---|---|
CPANEL_HOST | Your server's IP address or hostname | |
CPANEL_USER | Your cPanel/SSH username | |
CPANEL_PASS | Your SSH password | Mask as Masked & Protected |
CPANEL_PATH | Absolute path to your app's root directory (e.g., /home/user/public_html ) | |
SSH_PORT | 2223 (or your custom SSH port) |
Frontend Build Variables
These variables are safely passed to the npm run prod
command.
Key | Value |
---|---|
APP_URL | https://test.dkstore.id |
PUSHER_PORT | 443 |
PUSHER_SCHEME | https |
PUSHER_APP_CLUSTER | mt1 |
MIX_API_KEY | Your API key (Masked) |
DEMO | false |
2. The .gitlab-ci.yml
Pipeline
This two-stage pipeline separates building assets from deploying the application.
It creates a temporary, safe .env
file for the frontend build and uses rsync for efficient file transfer.
stages:
- build
- deploy
# Stage 1: Build Frontend Assets
build_assets:
stage: build
image: node:18
only:
- staging # Or your deployment branch
script:
- echo "Installing frontend dependencies..."
- npm install
- echo "Creating temporary .env for frontend build..."
- echo "APP_URL=${APP_URL}" > .env
- echo "PUSHER_PORT=${PUSHER_PORT}" >> .env
- echo "PUSHER_SCHEME=${PUSHER_SCHEME}" >> .env
- echo "PUSHER_APP_CLUSTER=${PUSHER_APP_CLUSTER}" >> .env
- echo "MIX_API_KEY=${MIX_API_KEY}" >> .env
- echo "DEMO=${DEMO}" >> .env
- echo "MIX_HOST=${APP_URL}" >> .env
- echo "VITE_PUSHER_PORT=${PUSHER_PORT}" >> .env
- echo "VITE_PUSHER_SCHEME=${PUSHER_SCHEME}" >> .env
- echo "VITE_PUSHER_APP_CLUSTER=${PUSHER_APP_CLUSTER}" >> .env
- echo "Building production assets..."
- npm run prod
artifacts:
paths:
- public/js/
- public/css/
expire_in: 1 hour
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
# Stage 2: Deploy Application to Server
deploy_to_production:
stage: deploy
image: ubuntu:22.04
before_script:
- apt-get update -qy && apt-get install -y sshpass rsync
script:
- echo "Deploying application with rsync..."
- sshpass -p "$CPANEL_PASS" rsync -avz -e "ssh -p $SSH_PORT -o StrictHostKeyChecking=no" \
--exclude ".git*" \
--exclude ".gitlab-ci.yml" \
--exclude "node_modules/" \
--exclude ".env" \
. $CPANEL_USER@$CPANEL_HOST:$CPANEL_PATH/
- echo "Running final commands on the server..."
- sshpass -p "$CPANEL_PASS" ssh -p $SSH_PORT $CPANEL_USER@$CPANEL_HOST "cd $CPANEL_PATH && php artisan optimize:clear && php artisan storage:link"
dependencies:
- build_assets