Why CI/CD Matters for Small Teams

If you're running a small tech team in Nepal — maybe a startup in Kathmandu or a digital agency in Lalitpur — you might think CI/CD is something "big companies" do. Here's the truth: CI/CD is even more valuable for small teams because every hour saved on manual work is an hour you can spend building features.

In this guide, I'll walk you through setting up a complete CI/CD pipeline with GitLab CI/CD, from zero to production deployment.

What Is GitLab CI/CD?

GitLab CI/CD is a built-in continuous integration and deployment tool. Every time you push code to your GitLab repository, it can automatically:

All defined in a single .gitlab-ci.yml file.

Prerequisites

Step 1: Create Your .gitlab-ci.yml

Create this file in your project root:

yaml
stages:
  - test
  - build
  - deploy

variables:
  DOCKER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

# Stage 1: Run tests
test:
  stage: test
  image: node:20-alpine
  script:
    - npm ci
    - npm run test
    - npm run lint
  rules:
    - if: $CI_MERGE_REQUEST_IID
    - if: $CI_COMMIT_BRANCH == "main"

# Stage 2: Build Docker image
build:
  stage: build
  image: docker:24.0
  services:
    - docker:24.0-dind
  script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - docker build -t $DOCKER_IMAGE .
    - docker push $DOCKER_IMAGE
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

# Stage 3: Deploy to production
deploy:
  stage: deploy
  image: alpine:latest
  script:
    - apk add --no-cache openssh-client
    - mkdir -p ~/.ssh
    - echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_ed25519
    - chmod 600 ~/.ssh/id_ed25519
    - ssh -o StrictHostKeyChecking=no [email protected] "
        docker pull $DOCKER_IMAGE &&
        docker stop app || true &&
        docker rm app || true &&
        docker run -d --name app -p 3000:3000 $DOCKER_IMAGE
      "
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
      when: manual

Step 2: Set Up CI/CD Variables

In your GitLab project, go to Settings → CI/CD → Variables and add:

VariableDescription
SSH_PRIVATE_KEYSSH key for connecting to your server
CI_REGISTRY_USERGitLab container registry username
CI_REGISTRY_PASSWORDGitLab container registry password

Step 3: Understand the Pipeline Flow

Here's what happens on each push to main:

mermaid
graph LR
    A[Push to main] --> B[test]
    B --> C[build]
    C --> D{deploy}
    D -->|manual trigger| E[Production]
  1. Test stage — Installs dependencies, runs tests and linting
  2. Build stage — Creates a Docker image and pushes to GitLab's registry
  3. Deploy stage — SSH's into your server and deploys the new image (manual trigger for safety)

Step 4: Add Security Scanning

For teams managing sensitive applications in Nepal (fintech, health tech, etc.), add a security scan:

yaml
security-scan:
  stage: test
  image: aquasec/trivy:latest
  script:
    - trivy image --severity HIGH,CRITICAL $DOCKER_IMAGE
  allow_failure: true
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

Real-World Results

At Andmine Pvt Ltd, where I work as a System Administrator, implementing CI/CD pipelines reduced our deployment time from ~45 minutes per deployment to under 3 minutes. Manual errors dropped to near zero, and our team gained confidence in shipping code on Fridays.

Common Pitfalls to Avoid

1. Not caching dependencies

yaml
test:
  cache:
    key: ${CI_COMMIT_REF_SLUG}
    paths:
      - node_modules/

2. Deploying on every branch

Use rules: to control when pipelines run. Don't deploy feature branches to production.

3. No manual approval for production

The when: manual flag on your deploy stage prevents accidental production deployments.

Next Steps

Once you have the basics working:


Need help setting up CI/CD for your team? I work with organizations across Nepal to build reliable deployment pipelines. Get in touch.