Deploy with a Single Git Push
Automate your entire deployment pipeline. Push to the "live" branch and watch GitLab CI/CD build, test, deploy to staging, then switch production with <1 second downtime .
git push origin live - that's it!
Test on staging subdomain before production
Review staging, then click "Deploy" in GitLab
Atomic symlink switch to production
How GitLab CI/CD Deployment Works
This automated pipeline builds on the manual blue-green deployment strategy, automating every step from code push to production switch.
Deployment Flow
- Trigger: Developer pushes to "live" branch
- Build: GitLab Runner builds Nuxt 4 app (pnpm build)
- Deploy to Staging: Upload to timestamped release folder, install deps, link shared resources
- Staging Health Check: Verify app works on staging subdomain (staging.example.com)
- Manual Approval: Developer reviews staging site, clicks "Deploy to Production" in GitLab UI
- Production Switch: Atomic symlink update + PM2 restart (<1 second downtime)
- Verify & Cleanup: Health check production, remove old releases
Pipeline Stages
Stage 1: Build
Installs dependencies, builds Nuxt 4 app, creates deployment artifact
Stage 2: Deploy Staging
SSHs to server, deploys to timestamped release, starts on staging subdomain
Stage 3: Deploy Production (Manual)
Waits for manual approval, then switches symlink and restarts PM2
Stage 4: Cleanup
Removes old releases (keeps last 3), cleans up temp files
๐ฏ Personalize This Guide
Enter your server details below to customize all commands throughout this guide. Your values will appear in highlighted text .
Prerequisites
Before setting up GitLab CI/CD deployment, ensure you have the following in place:
1. Server Setup
- Ubuntu server with Nuxt 4, PM2, NGINX, and Prisma configured
- Blue-green deployment directory structure in place
- SSH access configured with key-based authentication
- See Nuxt 4 + PM2 + NGINX Setup Guide for initial server configuration
2. GitLab Runner
- GitLab Runner installed on your server (or use GitLab shared runners)
- Runner configured with shell executor for SSH access
- Runner tagged appropriately (e.g., "production", "deployment")
3. GitLab CI/CD Variables
Configure these secret variables in GitLab: Settings โ CI/CD โ Variables
-
SSH_PRIVATE_KEY- Private key for SSH access to server -
SERVER_USER- Server username (e.g., "deploy") -
SERVER_HOST- Server IP or hostname -
PROD_PORT- Production port (e.g., 3101) -
STAGING_PORT- Staging port (e.g., 3102)
4. NGINX Configuration
Staging subdomain must be configured in NGINX to proxy to staging port:
# /etc/nginx/sites-available/{{deploy.staging || 'staging.example.com'}}
server {
listen 80;
server_name {{deploy.staging || 'staging.example.com'}};
location / {
proxy_pass http://127.0.0.1:{{deploy.stagingport || '3102'}};
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;
}
}
# Enable and reload
sudo ln -s /etc/nginx/sites-available/{{deploy.staging || 'staging.example.com'}} /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
5. PM2 Ecosystem Configuration
Ensure
ecosystem.config.cjs
supports both production and staging:
module.exports = {
apps: [{
name: '{{deploy.appname || 'my-app'}}',
script: './.output/server/index.mjs',
cwd: '/var/www/{{deploy.domain || 'example.com'}}/current',
instances: 'max',
exec_mode: 'cluster',
env_production: {
NODE_ENV: 'production',
PORT: {{deploy.prodport || '3101'}}
},
env_staging: {
NODE_ENV: 'production',
PORT: {{deploy.stagingport || '3102'}}
}
}]
};
GitLab CI/CD Pipeline Configuration
Create a
.gitlab-ci.yml
file in your repository root to define the automated deployment pipeline.
Complete .gitlab-ci.yml
# .gitlab-ci.yml - GitLab CI/CD Zero-Downtime Deployment
# Trigger: Push to "live" branch
stages:
- build
- deploy_staging
- deploy_production
- cleanup
variables:
DEPLOY_DIR_BASE: "/var/www/{{deploy.domain || 'example.com'}}"
APP_NAME: "{{deploy.appname || 'my-app'}}"
NODE_VERSION: "20.18.1"
# Build Stage: Compile Nuxt 4 application
build:
stage: build
image: node:20
only:
- live
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
# Note: .output/ is intentionally excluded from cache to avoid stale builds
# If code changes don't appear after deployment, the cache may be the issue
script:
- echo "๐ง Installing pnpm..."
- npm install -g pnpm@latest
- echo "๐ฆ Installing dependencies..."
- pnpm install --frozen-lockfile
# Optional: Clean build to avoid cache issues (uncomment if needed)
# - rm -rf .nuxt .output node_modules/.vite
- echo "๐จ Building Nuxt 4 application..."
- NETLIFY_BLOBS=false pnpm build
# Verify build succeeded
- test -f .output/server/index.mjs || (echo "โ Build failed - server output missing!" && exit 1)
- echo "๐ Preparing deployment package..."
- mkdir -p deploy-package
- cp -r .output deploy-package/
- cp package.json pnpm-lock.yaml deploy-package/
- cp -r prisma deploy-package/
- cp -r scripts deploy-package/
- cp ecosystem.config.cjs deploy-package/
- echo "โ
Build complete!"
artifacts:
name: "nuxt-build-${CI_COMMIT_SHORT_SHA}"
paths:
- deploy-package/
expire_in: 1 hour
# Deploy to Staging: Upload and start on staging subdomain
deploy_staging:
stage: deploy_staging
only:
- live
dependencies:
- build
before_script:
- 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- ssh-keyscan $SERVER_HOST >> ~/.ssh/known_hosts
- chmod 644 ~/.ssh/known_hosts
script:
- echo "๐ Deploying to staging environment..."
- VERSION=$(date +%Y%m%d_%H%M%S)
- RELEASE_DIR="${DEPLOY_DIR_BASE}/releases/${VERSION}"
- echo "๐ค Uploading build to server..."
- ssh ${SERVER_USER}@${SERVER_HOST} "mkdir -p ${RELEASE_DIR}"
- scp -r deploy-package/* ${SERVER_USER}@${SERVER_HOST}:${RELEASE_DIR}/
- echo "โ๏ธ Installing dependencies on server..."
- ssh ${SERVER_USER}@${SERVER_HOST} "
cd ${RELEASE_DIR} &&
source ~/.nvm/nvm.sh &&
nvm use ${NODE_VERSION} &&
pnpm install --prod --frozen-lockfile &&
pnpm dlx prisma generate &&
pnpm run preflight || true &&
pnpm run preflight:db || true &&
mkdir -p logs && chmod 775 logs
"
- echo "๐ Linking shared resources..."
- ssh ${SERVER_USER}@${SERVER_HOST} "
ln -sf ${DEPLOY_DIR_BASE}/shared/.env ${RELEASE_DIR}/.env &&
ln -sf ${DEPLOY_DIR_BASE}/shared/db.sqlite ${RELEASE_DIR}/prisma/db.sqlite
"
- echo "๐๏ธ Syncing database schema..."
- ssh ${SERVER_USER}@${SERVER_HOST} "
cd ${RELEASE_DIR} &&
source ~/.nvm/nvm.sh &&
nvm use ${NODE_VERSION} &&
pnpm dlx prisma db push || echo 'โ ๏ธ Schema already in sync'
"
- echo "๐ Updating staging symlink..."
- ssh ${SERVER_USER}@${SERVER_HOST} "
ln -sfn ${RELEASE_DIR} ${DEPLOY_DIR_BASE}/staging
"
- echo "โป๏ธ Starting/restarting staging PM2 process..."
- ssh ${SERVER_USER}@${SERVER_HOST} "
cd ${DEPLOY_DIR_BASE}/staging &&
pm2 delete ${APP_NAME}-staging || true &&
pm2 start ecosystem.config.cjs --name ${APP_NAME}-staging --env staging &&
pm2 save
"
- echo "๐งช Running staging health check..."
- sleep 5
- ssh ${SERVER_USER}@${SERVER_HOST} "
curl -f -I http://127.0.0.1:${STAGING_PORT}/ || (echo 'โ Staging health check failed!' && exit 1)
"
- echo "โ
Staging deployment complete!"
- echo "๐ Version: ${VERSION}"
- echo "๐ Staging URL: https://{{deploy.staging || 'staging.example.com'}}"
- echo ""
- echo "๐ Review staging site, then approve 'Deploy to Production' job in GitLab UI"
environment:
name: staging
url: https://{{deploy.staging || 'staging.example.com'}}
# Deploy to Production: Manual approval required
deploy_production:
stage: deploy_production
only:
- live
when: manual
dependencies: []
before_script:
- 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- ssh-keyscan $SERVER_HOST >> ~/.ssh/known_hosts
- chmod 644 ~/.ssh/known_hosts
script:
- echo "โก Deploying to production..."
- echo "This will switch the production symlink and restart PM2"
- echo "๐ Reading staging release path..."
- STAGING_PATH=$(ssh ${SERVER_USER}@${SERVER_HOST} "readlink ${DEPLOY_DIR_BASE}/staging")
- echo "Staging release: ${STAGING_PATH}"
- echo "โก ATOMIC SWITCH: Updating production symlink..."
- ssh ${SERVER_USER}@${SERVER_HOST} "
ln -sfn ${STAGING_PATH} ${DEPLOY_DIR_BASE}/current
"
- echo "โป๏ธ Graceful PM2 restart (follows symlink, <1s downtime)..."
- ssh ${SERVER_USER}@${SERVER_HOST} "
pm2 restart ${APP_NAME} --update-env &&
pm2 save
"
- echo "โ๏ธ Verifying production deployment..."
- sleep 3
- ssh ${SERVER_USER}@${SERVER_HOST} "
curl -f -I http://127.0.0.1:${PROD_PORT}/ || (echo 'โ Production health check failed!' && exit 1)
"
- CURRENT_VERSION=$(ssh ${SERVER_USER}@${SERVER_HOST} "readlink ${DEPLOY_DIR_BASE}/current | sed 's|.*/||'")
- echo "โ
Production deployment complete!"
- echo "๐ฆ Version: ${CURRENT_VERSION}"
- echo "๐ Production URL: https://{{deploy.domain || 'example.com'}}"
environment:
name: production
url: https://{{deploy.domain || 'example.com'}}
# Cleanup: Remove old releases
cleanup:
stage: cleanup
only:
- live
when: on_success
dependencies: []
before_script:
- 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- ssh-keyscan $SERVER_HOST >> ~/.ssh/known_hosts
- chmod 644 ~/.ssh/known_hosts
script:
- echo "๐งน Cleaning old releases..."
- ssh ${SERVER_USER}@${SERVER_HOST} "
cd ${DEPLOY_DIR_BASE}/releases &&
KEEP_COUNT=3 &&
RELEASE_COUNT=\$(ls -1 | wc -l) &&
if [ \$RELEASE_COUNT -gt \$KEEP_COUNT ]; then
ls -t | tail -n +\$((KEEP_COUNT + 1)) | xargs -r rm -rf &&
echo 'โ
Cleaned old releases (kept last '\$KEEP_COUNT')'
else
echo 'โ
No cleanup needed (only '\$RELEASE_COUNT' releases)'
fi
"
- ACTIVE_COUNT=$(ssh ${SERVER_USER}@${SERVER_HOST} "ls -1 ${DEPLOY_DIR_BASE}/releases | wc -l")
- echo "๐ Active releases: ${ACTIVE_COUNT}"
- Only triggers on "live" branch pushes
- Builds once, deploys to staging automatically
- Requires manual approval before production switch
- Shares the same release between staging and production
- Atomic symlink switch ensures zero downtime
- Automatic cleanup keeps last 3 releases
Setting Up GitLab Runner
For this pipeline to work, you need a GitLab Runner configured on your server (or use GitLab's shared runners with SSH access).
Option 1: Install GitLab Runner on Your Server (Recommended)
# SSH into your server
ssh {{deploy.sudouser || 'username'}}@{{deploy.domain || deploy.ipaddress || 'your-server'}}
# Install GitLab Runner
curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | sudo bash
sudo apt-get install gitlab-runner
# Register runner
sudo gitlab-runner register
# Follow prompts:
# - GitLab instance URL: https://gitlab.com (or your instance)
# - Registration token: Get from GitLab โ Settings โ CI/CD โ Runners
# - Description: production-deploy
# - Tags: production,deployment
# - Executor: shell
# Start runner
sudo gitlab-runner start
# Verify
sudo gitlab-runner status
Option 2: Use GitLab Shared Runners with SSH
If using GitLab's shared runners, the pipeline will SSH into your server. Ensure:
-
SSH_PRIVATE_KEYvariable is set in GitLab -
Corresponding public key is in server's
~/.ssh/authorized_keys - Server firewall allows SSH from GitLab runner IPs
Configure GitLab CI/CD Variables
In GitLab: Settings โ CI/CD โ Variables โ Add Variable
| Variable | Value | Protected | Masked |
|---|---|---|---|
SSH_PRIVATE_KEY |
Your private SSH key (entire content) | โ | โ |
SERVER_USER |
{{deploy.sudouser || 'deploy'}} | โ | โ |
SERVER_HOST |
{{deploy.ipaddress || deploy.domain || '192.168.1.100'}} | โ | โ |
PROD_PORT |
{{deploy.prodport || '3101'}} | โ | โ |
STAGING_PORT |
{{deploy.stagingport || '3102'}} | โ | โ |
Deployment Workflow
Once everything is configured, deploying is as simple as pushing to the "live" branch.
Step 1: Create "live" Branch (One-Time)
# In your local repository
git checkout main
git checkout -b live
git push -u origin live
# Protect the branch in GitLab
# Settings โ Repository โ Protected Branches โ Add "live"
Step 2: Deploy Your Changes
# Make your changes on main or feature branch
git checkout main
# ... make changes, commit ...
# Merge to live branch to trigger deployment
git checkout live
git merge main
git push origin live
# GitLab CI/CD pipeline will automatically:
# 1. Build the app
# 2. Deploy to staging
# 3. Wait for your approval
Step 3: Review Staging
- Go to GitLab โ CI/CD โ Pipelines
- Click on the running pipeline
- Wait for "deploy_staging" job to complete
-
Visit
https:// {{deploy.staging || 'staging.example.com'}} - Test your changes thoroughly
Step 4: Deploy to Production
- In the pipeline view, find the "deploy_production" job
- Click the "Play" button (โถ) to approve deployment
- Watch the logs as it switches production symlink and restarts PM2
-
Verify at
https:// {{deploy.domain || 'example.com'}}
Rollback Strategy
If something goes wrong in production, you have multiple rollback options:
Option 1: Re-deploy Previous Version via GitLab
# Revert the merge commit
git checkout live
git revert HEAD
git push origin live
# Or reset to previous commit
git reset --hard HEAD~1
git push -f origin live
# Pipeline will deploy the previous version
Option 2: Manual Symlink Rollback (Instant)
# SSH into server
ssh {{deploy.sudouser || 'username'}}@{{deploy.domain || deploy.ipaddress || 'your-server'}}
# List available releases
ls -lt /var/www/{{deploy.domain || 'example.com'}}/releases/
# Get previous version
CURRENT=$(readlink /var/www/{{deploy.domain || 'example.com'}}/current | sed 's|.*/||')
PREVIOUS=$(ls -t /var/www/{{deploy.domain || 'example.com'}}/releases/ | grep -v "^${CURRENT}$" | head -1)
echo "Current: $CURRENT"
echo "Rolling back to: $PREVIOUS"
# Atomic rollback
ln -sfn "/var/www/{{deploy.domain || 'example.com'}}/releases/$PREVIOUS" /var/www/{{deploy.domain || 'example.com'}}/current
pm2 restart {{deploy.appname || 'my-app'}} --update-env
# Verify
curl -I http://127.0.0.1:{{deploy.prodport || '3101'}}/
echo "โ
Rolled back to $PREVIOUS"
Option 3: Re-run Old Pipeline
- Go to GitLab โ CI/CD โ Pipelines
- Find the successful pipeline you want to re-deploy
- Click "Retry" (๐)
- Approve the production deployment
Advanced: Database Migrations in CI/CD
Handling database migrations requires extra care in automated deployments.
Strategy 1: Auto-Migrate in Pipeline (Simple)
The example pipeline already includes
pnpm dlx prisma db push
in the staging deploy. This works for development but can be risky in production.
Strategy 2: Manual Migration Approval (Safer)
Add a separate manual migration stage:
# Add to .gitlab-ci.yml after deploy_staging
migrate_database:
stage: deploy_staging
only:
- live
when: manual
allow_failure: false
before_script:
# ... same SSH setup ...
script:
- echo "๐๏ธ Applying database migrations..."
- STAGING_PATH=$(ssh ${SERVER_USER}@${SERVER_HOST} "readlink ${DEPLOY_DIR_BASE}/staging")
- ssh ${SERVER_USER}@${SERVER_HOST} "
cd ${STAGING_PATH} &&
source ~/.nvm/nvm.sh &&
nvm use ${NODE_VERSION} &&
# Backup database first
cp ${DEPLOY_DIR_BASE}/shared/db.sqlite ${DEPLOY_DIR_BASE}/shared/db.sqlite.backup-\$(date +%Y%m%d_%H%M%S) &&
# Apply migrations
pnpm dlx prisma migrate deploy &&
pnpm dlx prisma generate
"
- echo "โ
Database migrated successfully"
Strategy 3: Backward-Compatible Migrations (Best)
Design migrations that work with both old and new code:
- Adding columns: Make them nullable or have defaults
- Removing columns: Deploy code that doesn't use them first, remove later
- Renaming: Create new column, migrate data, remove old column in next release
Monitoring & Notifications
Stay informed about your deployments with GitLab's built-in notifications and integrations.
Email Notifications
GitLab automatically emails you when pipelines fail. Configure in: User Settings โ Notifications
Slack Integration
# Add to .gitlab-ci.yml
notify_slack:
stage: cleanup
only:
- live
when: on_success
script:
- |
curl -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"โ
Deployment successful! Version: ${CI_COMMIT_SHORT_SHA}\"}" \
$SLACK_WEBHOOK_URL
GitLab Environments
The pipeline already defines "staging" and "production" environments. View deployment history in: GitLab โ Deployments โ Environments
PM2 Monitoring
Monitor your application post-deployment:
# Add health monitoring job
monitor_production:
stage: cleanup
only:
- live
when: on_success
script:
- echo "๐ Checking PM2 status..."
- ssh ${SERVER_USER}@${SERVER_HOST} "pm2 list"
- ssh ${SERVER_USER}@${SERVER_HOST} "pm2 logs ${APP_NAME} --lines 50 --nostream"
Troubleshooting
๐จ Code Changes Not Appearing After Deployment
Causes and Solutions:
1. GitLab CI/CD Cache Issue
# In .gitlab-ci.yml, temporarily disable cache for one build:
build:
stage: build
cache: {} # Disable cache
# ... rest of config
# Or clear cache in GitLab UI:
# CI/CD โ Pipelines โ Clear Runner Caches
# After successful deployment, re-enable cache
2. Service Worker Caching (if PWA was enabled)
# Users need to clear service workers:
# Chrome/Edge: F12 โ Application โ Service Workers โ Unregister
# Firefox: about:serviceworkers โ Unregister
# Or disable PWA module entirely in nuxt.config.ts:
modules: [
"@nuxt/eslint",
# ... other modules
// "@vite-pwa/nuxt", // DISABLED
],
Pipeline Fails at Build Stage
"Cannot access 'sessionHooks' before initialization"
# Ensure you have the patch-sessionhooks module in your project
# File: modules/patch-sessionhooks.ts
# This module fixes export order issues with nuxt-auth-utils
# If error persists, rebuild with clean cache:
# In .gitlab-ci.yml, add before build:
- rm -rf .nuxt .output node_modules/.vite
PWA Build Errors
# Disable PWA module if not needed
# In nuxt.config.ts:
modules: [
"@nuxt/eslint",
"@nuxt/image",
"@nuxt/content",
"@nuxt/ui",
"./modules/patch-sessionhooks",
// "@vite-pwa/nuxt", // DISABLED - causing build issues
],
# Commit and push to trigger new build
Empty .output/server Folder
# Build failed silently - check GitLab build logs carefully
# Common causes:
# - PWA module errors (disable if not needed)
# - TypeScript errors (add typecheck stage before build)
# - Missing dependencies
# - Environment variables not set
# Add build verification to pipeline:
- test -f .output/server/index.mjs || exit 1
General Build Failures
# Check build logs in GitLab UI
# Common issues:
# - Missing dependencies in package.json
# - TypeScript errors
# - Environment variables not set
# Test build locally first:
pnpm install
pnpm build
# Add linting and type checking stages:
lint:
stage: build
script:
- pnpm install
- pnpm lint
- pnpm typecheck
SSH Connection Issues
# Verify SSH key is correct
# In GitLab variables, ensure SSH_PRIVATE_KEY has:
# - Full key including -----BEGIN/END----- lines
# - No extra spaces or newlines
# Test SSH access manually:
ssh -i ~/.ssh/your_key {{deploy.sudouser || 'username'}}@{{deploy.ipaddress || 'your-server'}}
# Check server's authorized_keys
cat ~/.ssh/authorized_keys
Staging Health Check Fails
# SSH into server and check staging process
ssh {{deploy.sudouser || 'username'}}@{{deploy.ipaddress || 'your-server'}}
# Check PM2 logs
pm2 logs {{deploy.appname || 'my-app'}}-staging --lines 100
# Test staging port manually
curl -I http://127.0.0.1:{{deploy.stagingport || '3102'}}/
# Common issues:
# - Port already in use
# - Missing .env file
# - Database connection issues
# - PM2 process crashed
Production Switch Fails
# Check current symlink
ssh {{deploy.sudouser || 'username'}}@{{deploy.ipaddress || 'your-server'}}
readlink /var/www/{{deploy.domain || 'example.com'}}/current
# Check PM2 status
pm2 list
pm2 logs {{deploy.appname || 'my-app'}} --lines 50
# Verify PM2 is using symlink path
pm2 describe {{deploy.appname || 'my-app'}} | grep cwd
# Should show: /var/www/{{deploy.domain || 'example.com'}}/current
GitLab Runner Not Picking Up Jobs
# Check runner status
sudo gitlab-runner status
# Verify runner is registered
sudo gitlab-runner list
# Check runner tags match .gitlab-ci.yml
# In GitLab: Settings โ CI/CD โ Runners
# Restart runner
sudo gitlab-runner restart
Common Questions
Why do PM2 logs show "CSP Disabled"?
This is normal and expected. Content Security Policy (CSP) is an optional security feature
disabled by default. To enable it, add
CSP_ENABLED=true
to your shared/.env file on the server and redeploy.
What if code changes don't appear after deployment?
This usually means GitLab's cache is stale. Solutions:
- Clear runner caches in GitLab UI (CI/CD โ Pipelines โ Clear Runner Caches)
- Temporarily disable cache in .gitlab-ci.yml for one build
-
Add
rm -rf .nuxt .outputbefore the build step
Should I use GitLab shared runners or install my own?
Shared Runners:
Easier setup, but requires SSH from GitLab to your server.
Self-hosted Runner:
More control, no external SSH needed, but requires runner installation on your server.
For production deployments, self-hosted runners on the same server are recommended for
security and speed.
Best Practices
Branch Protection
- Protect "live" branch in GitLab (Settings โ Repository โ Protected Branches)
- Require merge requests for changes to "live"
- Require approvals before merging to "live"
- Only allow maintainers to push to "live"
Testing Before Deployment
-
Run tests in build stage (add
pnpm testbefore build) -
Lint code (add
pnpm lintstage) -
Type check (add
nuxt typecheck) - Only deploy if all tests pass
Deployment Timing
- Deploy during low-traffic hours for first few times
- Monitor application after deployment
- Keep team informed via Slack/email notifications
- Have rollback plan ready
Resource Management
- Set artifact expiration (1 hour in example)
- Clean old releases automatically (keeps last 3)
- Monitor server disk space
- Consider using GitLab's cache for faster builds
Security
- Use protected and masked variables for secrets
- Rotate SSH keys regularly
- Limit SSH access to specific IPs if possible
- Use separate deploy user with limited permissions
- Enable 2FA on GitLab account
Why GitLab CI/CD Wins
For Developers
- One Command: git push origin live - that's it!
- Staging Review: Test on real server before production
- Safe Approval: Manual gate before production switch
- Full Visibility: See every deployment step in GitLab UI
- Easy Rollback: Revert commit or re-run old pipeline
For Operations
- Consistency: Every deployment follows same process
- Audit Trail: Full history of who deployed what when
- Zero Downtime: <1 second production switch
- Automated Cleanup: Old releases removed automatically
- Monitoring: Health checks at every stage
Next Steps
- โ Set up server with blue-green directory structure (see manual deployment guide )
- โ Install and configure GitLab Runner on your server
- โ Add staging subdomain to NGINX configuration
- โ Configure CI/CD variables in GitLab
-
โ
Create
.gitlab-ci.ymlin your repository - โ Create and protect "live" branch
- โ Test with a small change: merge to live, verify staging, approve production
- โ Set up Slack notifications (optional)
- โ Document your deployment process for team members