CI/CD Integration

GitLab CI/CD Pipeline

Automate deployments to Focal Deploy with GitLab Pipelines

15-20 minutesIntermediate

Prerequisites

  • GitLab Repository

    Your application code hosted on GitLab.com or self-hosted GitLab

  • Focal Deploy Account & Project

    An existing project deployed at least once manually (follow this guide)

  • Focal Deploy API Key

    Generate from Dashboard → API Keys

Step 1: Generate a Focal Deploy API Key

  1. 1
  2. 2

    Click "Generate New API Key"

  3. 3

    Give it a descriptive name like "GitLab CI - My Project"

  4. 4

    Copy the generated API key - you'll need it in the next step

    Important: The API key will only be shown once. Store it securely!

Step 2: Add CI/CD Variables to GitLab

  1. 1

    Go to your GitLab project → SettingsCI/CDVariables

  2. 2

    Click "Add variable" and create the following variables:

    Variable #1:

    Key:

    FOCAL_DEPLOY_API_KEY

    Value:

    Your API key from Step 1

    ✅ Check "Protect variable" and "Mask variable"

    Variable #2 (Optional):

    Key:

    FOCAL_DEPLOY_PROJECT_ID

    Value:

    Your project ID from Focal Deploy dashboard

Step 3: Create GitLab CI/CD Pipeline

Choose between a simple pipeline or an advanced one with staging, previews, and manual production deployment:

Option A: Simple Pipeline

Perfect for getting started. Deploys on every push to main branch.

# Simple GitLab CI/CD for Focal Deploy

stages:
  - deploy

deploy:
  stage: deploy
  image: curlimages/curl:latest
  script:
    - |
      curl -X POST https://api.focuswithfocal.io/api/deployments \
        -H "Authorization: Bearer $FOCAL_DEPLOY_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{"projectName": "my-app"}'
  only:
    - main

Option B: Advanced Pipeline

Includes testing, multiple environments, preview deployments for MRs, and manual production deployment.

# GitLab CI/CD Pipeline for Focal Deploy

stages:
  - test
  - build
  - deploy

variables:
  NODE_VERSION: "20"

# Cache node_modules for faster builds
cache:
  paths:
    - node_modules/

# Run tests
test:
  stage: test
  image: node:${NODE_VERSION}
  script:
    - npm ci
    - npm run lint
    - npm test
  only:
    - merge_requests
    - main
    - develop

# Build application
build:
  stage: build
  image: node:${NODE_VERSION}
  script:
    - npm ci
    - npm run build
  artifacts:
    paths:
      - build/
      - dist/
      - .next/
    expire_in: 1 hour
  only:
    - main
    - develop

# Deploy to production
deploy_production:
  stage: deploy
  image: curlimages/curl:latest
  script:
    - |
      curl -X POST https://api.focuswithfocal.io/api/deployments \
        -H "Authorization: Bearer $FOCAL_DEPLOY_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{"projectId": "'"$FOCAL_DEPLOY_PROJECT_ID"'", "environment": "production", "branch": "'"$CI_COMMIT_REF_NAME"'"}'
  environment:
    name: production
    url: https://${FOCAL_DEPLOY_PROJECT_ID}.focuswithfocal.com
  only:
    - main
  when: manual

# Deploy to staging automatically
deploy_staging:
  stage: deploy
  image: curlimages/curl:latest
  script:
    - |
      curl -X POST https://api.focuswithfocal.io/api/deployments \
        -H "Authorization: Bearer $FOCAL_DEPLOY_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{"projectId": "'"$FOCAL_DEPLOY_PROJECT_ID"'", "environment": "staging", "branch": "'"$CI_COMMIT_REF_NAME"'"}'
  environment:
    name: staging
    url: https://staging-${FOCAL_DEPLOY_PROJECT_ID}.focuswithfocal.com
  only:
    - develop

# Preview deployments for merge requests
deploy_preview:
  stage: deploy
  image: curlimages/curl:latest
  script:
    - |
      curl -X POST https://api.focuswithfocal.io/api/deployments \
        -H "Authorization: Bearer $FOCAL_DEPLOY_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{"projectId": "'"$FOCAL_DEPLOY_PROJECT_ID"'", "environment": "preview-mr-'"$CI_MERGE_REQUEST_IID"'", "branch": "'"$CI_COMMIT_REF_NAME"'"}'
    - echo "Preview URL https://mr-$CI_MERGE_REQUEST_IID-${FOCAL_DEPLOY_PROJECT_ID}.focuswithfocal.com"
  environment:
    name: preview/mr-$CI_MERGE_REQUEST_IID
    url: https://mr-$CI_MERGE_REQUEST_IID-${FOCAL_DEPLOY_PROJECT_ID}.focuswithfocal.com
    on_stop: stop_preview
  only:
    - merge_requests

# Stop preview deployments when MR is closed
stop_preview:
  stage: deploy
  image: curlimages/curl:latest
  script:
    - curl -X DELETE "https://api.focuswithfocal.io/api/deployments/preview-mr-$CI_MERGE_REQUEST_IID" -H "Authorization: Bearer $FOCAL_DEPLOY_API_KEY"
  environment:
    name: preview/mr-$CI_MERGE_REQUEST_IID
    action: stop
  when: manual
  only:
    - merge_requests

📝 To add this pipeline:

  1. Create a new file in your repository root: .gitlab-ci.yml
  2. Paste one of the pipelines above
  3. Customize it for your project (change Node version, test commands, etc.)
  4. Commit and push to your repository

Step 4: Test Your Pipeline

  1. 1

    Push a commit to your main branch (or create a merge request if using the advanced pipeline)

    git add .gitlab-ci.yml
    git commit -m "Add GitLab CI/CD deployment"
    git push origin main
  2. 2

    Go to your repository → CI/CDPipelines to watch the pipeline run

  3. 3

    Click on the pipeline to see the job logs and track progress

  4. 4

    If using the advanced pipeline, manually trigger the production deployment from the pipeline view

  5. 5

    Verify your deployment in the Focal Deploy dashboard

Success!

Your pipeline is now active. Every push will trigger automated tests, builds, and deployments!

Advanced Configuration Options

Deploy Only on Git Tags

Trigger deployments only when you create a release tag:

deploy:
  stage: deploy
  script:
    - curl -X POST https://api.focuswithfocal.io/api/deployments -H "Authorization: Bearer $API_KEY"
  only:
    - tags  # Runs only on git tags
  except:
    - branches

Parallel Test Jobs

Speed up testing by running tests in parallel:

test:
  stage: test
  parallel: 3  # Run 3 instances in parallel
  script:
    - npm run test:$CI_NODE_INDEX

Save Build Artifacts

Keep build artifacts for debugging:

build:
  stage: build
  script:
    - npm run build
  artifacts:
    paths:
      - dist/
      - coverage/
    expire_in: 1 week

Slack Notifications

Get notified on Slack when deployments complete:

deploy:
  stage: deploy
  script:
    - curl -X POST https://api.focuswithfocal.io/api/deployments -H "Authorization: Bearer $API_KEY"
  after_script:
    - 'curl -X POST -H "Content-type: application/json" --data "{\"text\":\"Deployment $CI_JOB_STATUS\"}" $SLACK_WEBHOOK'

GitLab-Specific Features

Environment Management

GitLab's environment tracking lets you see deployment history and URLs:

View environments in your project → DeploymentsEnvironments

Protected Environments

Restrict who can deploy to production by setting up protected environments in SettingsCI/CDProtected Environments

Merge Request Approvals

Require approvals before deploying to production by configuring merge request approval rules

Dynamic Environments

Create temporary environments for each merge request that auto-delete when merged:

environment:
  name: review/$CI_COMMIT_REF_SLUG
  url: https://$CI_COMMIT_REF_SLUG.focuswithfocal.com
  on_stop: stop_review
  auto_stop_in: 1 day

Security Best Practices

Always use masked and protected variables

Enable "Mask variable" and "Protect variable" for all sensitive values

Use manual deployments for production

Add when: manual to production jobs to prevent accidental deployments

Limit variable scope

Set variables to specific environments when possible (production, staging, etc.)

Enable protected branches

Protect main/production branches and require merge requests for all changes

Troubleshooting

Pipeline Fails with "Variable not set"

Check that:

  • Variable names match exactly (case-sensitive)
  • Variables are not set to "protected" only if you're testing on a non-protected branch
  • No extra spaces in variable values

Pipeline Never Starts

Verify .gitlab-ci.yml syntax at CI/CDEditorValidate

Deployment Job Skipped

Check the only and except rules match your branch or tag

Artifact Download Fails

Ensure the build job completes successfully and artifacts haven't expired

Related Guides