ติดตั้ง SonarQube บน VPS ตรวจคุณภาพโค้ด

ติดตั้ง SonarQube บน VPS ตรวจคุณภาพโค้ด

มิถุนายน 2026 · อ่าน 9 นาที

SonarQube คือแพลตฟอร์มวิเคราะห์คุณภาพโค้ดระดับองค์กร — ตรวจหา bug security vulnerability code smell และ duplication อัตโนมัติ ในโปรเจกต์ซอฟต์แวร์ของคุณ รองรับเกือบ 30 ภาษาโปรแกรม (Java, Python, PHP, JavaScript, Go, C++, C#, TypeScript) และติดตั้งง่ายบน VPS ใด้ๆ ด้วย Docker Compose บทความนี้สอนตั้ง SonarQube Server + SonarScanner จากศูนย์ สำหรับ DevOps team ที่ต้องการ continuous quality gate ในขั้นตอน CI/CD พร้อมเชื่อมต่อกับ Jenkins และ GitLab CI

สถาปัตยกรรม SonarQube

ติดตั้ง SonarQube ด้วย Docker Compose

mkdir -p /opt/sonarqube/{data,extensions,logs} && cd /opt/sonarqube

# ตั้ง sysctl เพื่อให้ Elasticsearch (ในตัว SonarQube) ทำงานได้
sudo sysctl -w vm.max_map_count=262144
echo "vm.max_map_count=262144" | sudo tee -a /etc/sysctl.conf

# สร้าง docker-compose.yml
cat > docker-compose.yml << 'EOF'
version: '3.8'

services:
  db:
    image: postgres:15-alpine
    restart: unless-stopped
    environment:
      POSTGRES_DB: sonarqube
      POSTGRES_USER: sonar
      POSTGRES_PASSWORD: CHANGE_ME_DB_PASSWORD
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "sonar"]
      interval: 10s
      timeout: 5s
      retries: 5

  sonarqube:
    image: sonarqube:10-community
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    ports:
      - "127.0.0.1:9000:9000"
    environment:
      SONAR_JDBC_URL: jdbc:postgresql://db:5432/sonarqube
      SONAR_JDBC_USERNAME: sonar
      SONAR_JDBC_PASSWORD: CHANGE_ME_DB_PASSWORD
      SONAR_PYTHON_COVERAGE_REPORT_PATHS: coverage.xml
      SONAR_COVERAGE_EXCLUSIONS: "**/tests/**,**/node_modules/**"
    volumes:
      - sonarqube_data:/opt/sonarqube/data
      - sonarqube_extensions:/opt/sonarqube/extensions
      - sonarqube_logs:/opt/sonarqube/logs
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/api/system/health"]
      interval: 15s
      timeout: 10s
      retries: 5

volumes:
  postgres_data:
  sonarqube_data:
  sonarqube_extensions:
  sonarqube_logs:
EOF

# สตาร์ท service ทั้งหมด
docker-compose up -d

# รอให้ SonarQube เตรียมพร้อม (~60 วินาที)
echo "Waiting for SonarQube to be ready..."
sleep 60

ตั้ง Nginx Reverse Proxy + HTTPS

sudo nano /etc/nginx/sites-available/sonarqube

server {
    listen 80;
    server_name sonarqube.yourdomain.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name sonarqube.yourdomain.com;

    ssl_certificate     /etc/letsencrypt/live/sonarqube.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/sonarqube.yourdomain.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;

    location / {
        proxy_pass http://127.0.0.1:9000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 60s;
    }

    location ~ ^/api/ce/info$ {
        proxy_pass http://127.0.0.1:9000;
        proxy_cache_valid 200 10m;
        proxy_cache_bypass $http_pragma $http_authorization;
        add_header X-Cache-Status $upstream_cache_status;
    }
}

# เปิดใช้งาน
sudo ln -s /etc/nginx/sites-available/sonarqube /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

# สร้าง SSL certificate
sudo certbot --nginx -d sonarqube.yourdomain.com

เพิ่ม SonarQube Token และตั้งการยืนยันตัวตน

# เปิด https://sonarqube.yourdomain.com
# login: admin / admin (default password อันตรายจริงๆ ห้ามใช้ production)

# เปลี่ยน password admin:
# Admin → Security → Change password

# สร้าง token สำหรับ Scanner:
# Admin → Security → Users → admin (click) → Generate Tokens
# กำหนดชื่อ "jenkins-scanner" → Copy token (ใช้กับ Jenkins/GitLab CI)

# ตั้ง Quality Gate:
# Quality Gates → Create
# ตั้ง gate condition:
#   - Coverage on New Code >= 80%
#   - Reliability Rating A or better
#   - Security Rating A or better
#   - Bugs = 0 (on new code)

# ตั้ง webhook เพื่อ notify Jenkins/GitLab ว่าการวิเคราะห์เสร็จแล้ว:
# Administration → Configuration → Webhooks
# URL: https://jenkins.yourdomain.com/sonarqube-webhook/
# Events: Project Analysis Completed

ติดตั้ง SonarScanner บน Build Server

# ถ้าใช้ Jenkins หรือ Linux build agent ติดตั้ง SonarScanner CLI
cd /opt
sudo wget https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-5.0.1.3006-linux-x64.zip
sudo unzip sonar-scanner-cli-*.zip
sudo rm sonar-scanner-cli-*.zip
sudo mv sonar-scanner-* sonar-scanner
sudo chown -R nobody:nogroup /opt/sonar-scanner

# เพิ่ม PATH
echo "export PATH=/opt/sonar-scanner/bin:\$PATH" | sudo tee -a /etc/profile.d/sonar-scanner.sh
source /etc/profile.d/sonar-scanner.sh

# ตั้งค่า Server URL ใน sonar-scanner.properties
sudo nano /opt/sonar-scanner/conf/sonar-scanner.properties

# เพิ่มบรรทัด:
sonar.host.url=https://sonarqube.yourdomain.com
sonar.login=YOUR_SONARQUBE_TOKEN

# ตรวจสอบการติดตั้ง
sonar-scanner --version

สแกนโปรเจกต์ตัวอย่าง

# สร้างโปรเจกต์ตัวอย่าง
mkdir -p /tmp/myapp && cd /tmp/myapp

# สร้างไฟล์ Python ที่มี bug
cat > app.py << 'PYEOF'
def calculate_discount(price, discount=0):
    result = price * (1 - discount)
    # bug: unused variable
    unnecessary = 123
    return result

def check_user(user):
    # bug: potential null pointer
    return user.name.upper()

class Config:
    # code smell: hardcoded password
    PASSWORD = "hardcoded123456"
PYEOF

# สร้าง sonar-project.properties
cat > sonar-project.properties << 'PROPSEOF'
sonar.projectKey=myapp
sonar.projectName=My App
sonar.projectVersion=1.0.0
sonar.sources=.
sonar.sourceEncoding=UTF-8
sonar.exclusions=**/node_modules/**,**/tests/**,**/.env
PROPSEOF

# รันการสแกน
sonar-scanner \
  -Dsonar.host.url=https://sonarqube.yourdomain.com \
  -Dsonar.login=YOUR_TOKEN \
  -Dsonar.projectKey=myapp

# รอสักครู่แล้วเปิด dashboard:
# https://sonarqube.yourdomain.com/projects

เชื่อมต่อกับ Jenkins Pipeline

# ใน Jenkins: Manage Jenkins → Plugins → Search "SonarQube Scanner for Jenkins"
# ติดตั้ง plugin นี้

# ตั้งค่า SonarQube Server ใน Jenkins:
# Manage Jenkins → System Configuration → SonarQube Servers
# Name: SonarQube
# Server URL: https://sonarqube.yourdomain.com
# Server authentication token: (paste your token)

# ใน Jenkinsfile:
pipeline {
    agent any

    stages {
        stage('Build') {
            steps {
                sh 'mvn clean package -DskipTests'
            }
        }

        stage('SonarQube Analysis') {
            environment {
                SONAR_TOKEN = credentials('sonarqube-token')
            }
            steps {
                sh '''
                    sonar-scanner \
                      -Dsonar.host.url=https://sonarqube.yourdomain.com \
                      -Dsonar.projectKey=myproject \
                      -Dsonar.login=$SONAR_TOKEN \
                      -Dsonar.sources=src \
                      -Dsonar.exclusions=**/tests/**
                '''
            }
        }

        stage('Quality Gate') {
            steps {
                waitForQualityGate abortPipeline: true
            }
        }
    }
}

เชื่อมต่อกับ GitLab CI

# ใน .gitlab-ci.yml
stages:
  - build
  - sonarqube

variables:
  SONAR_HOST_URL: https://sonarqube.yourdomain.com
  SONAR_TOKEN: $SONARQUBE_TOKEN

sonarqube-check:
  stage: sonarqube
  image: sonarsource/sonar-scanner-cli:latest
  script:
    - sonar-scanner
      -Dsonar.projectKey=$CI_PROJECT_NAME
      -Dsonar.sources=.
      -Dsonar.host.url=$SONAR_HOST_URL
      -Dsonar.login=$SONAR_TOKEN
      -Dsonar.gitlab.project_id=$CI_PROJECT_PATH
      -Dsonar.gitlab.commit_sha=$CI_COMMIT_SHA
      -Dsonar.gitlab.ref_name=$CI_COMMIT_REF_NAME
  only:
    - merge_requests
    - develop
    - main

# เพิ่ม SONARQUBE_TOKEN ใน GitLab CI/CD variables:
# Settings → CI/CD → Variables
# ค่า: YOUR_SONARQUBE_TOKEN (ที่สร้างไว้ก่อนหน้า)

ตั้ง Custom Rules และ Quality Profile

# Quality Profiles เป็นชุด rule ที่ใช้วิเคราะห์โค้ด
# Admin → Quality Profiles → เลือก language (Python, Java, JavaScript, etc.)

# สร้าง custom profile สำหรับ project:
# Quality Profiles → Create → ตั้งชื่อ "MyCompanyRules-Python"
# Parent Profile: "Sonar Way" (default)
# Activate Rules: เลือก rules ที่ต้องการ (OWASP Top 10, CWE, Bug-prone ฯลฯ)

# ตั้ง rule severity:
# - Blocker: Build failure (ห้ามผ่าน quality gate)
# - Critical: Security vulnerabilities
# - Major: Bug ที่มีผลต่อ logic
# - Minor: Code style, non-critical issues
Tip: ในการตั้ง production SonarQube ให้ใช้ PostgreSQL แทน Embedded Database เสมอ — Embedded H2 ดำเนินการ rescan ช้าเมื่อโปรเจกต์มีขนาดใหญ่ และอาจสูญหายข้อมูลได้หากเซิร์ฟเวอร์ขัดข้อง ส่วน PostgreSQL นั้นเสถียรและปรับขนาดได้

ตรวจสอบว่า SonarQube ทำงานถูกต้อง

# ตรวจสอบ health status ของ SonarQube Server
curl -s http://localhost:9000/api/system/health | python3 -m json.tool

# ต้องได้ status: UP

# ตรวจสอบ container ทั้งหมด
docker-compose ps

# ดู logs ของ SonarQube
docker-compose logs sonarqube -f --tail=50

# ตรวจสอบ PostgreSQL database
docker-compose exec db psql -U sonar -d sonarqube -c "SELECT COUNT(*) FROM projects;"

# ตรวจสอบทำงานผ่าน reverse proxy
curl -I https://sonarqube.yourdomain.com

# ต้องได้ HTTP/1.1 200 OK
Tip: ถ้า SonarQube ช้า ให้ตรวจสอบ Elasticsearch (embedded) — เปิด dashboard และดู log จำนวน index segments หากมากกว่า 100 ให้รัน optimize (Admin → System → Maintenance → Reindex สำหรับ old analysis)

แก้ปัญหาที่พบบ่อย

Error: "Elasticsearch [elasticsearch.errors.ConnectionError]"

Elasticsearch ในตัว SonarQube ตั้ง IP listener ผิด หรือ port ถูกยึดโดยโปรแกรมอื่น

# ตรวจ vm.max_map_count ต้องเป็น 262144 ขึ้นไป
sysctl vm.max_map_count

# ถ้าไม่ถูก ให้ตั้งใหม่
sudo sysctl -w vm.max_map_count=262144

# ลบ container และ volume ที่เก่า แล้วรัน docker-compose up -d ใหม่
docker-compose down -v
docker-compose up -d

Error: "Database connection error" เมื่อเชื่อมต่อกับ PostgreSQL

ชื่อผู้ใช้ password หรือ URL database ไม่ถูกต้อง หรือ PostgreSQL container ยังไม่พร้อม

# ตรวจสอบ PostgreSQL กำลังทำงาน
docker-compose ps db

# ต้องเห็น "Up (healthy)"

# ตรวจเชื่อมต่อ database จากข้างใน SonarQube container
docker-compose exec sonarqube psql -h db -U sonar -d sonarqube -c "SELECT 1"

# ถ้าผิด environment variable ต้องแก้ docker-compose.yml และ restart
docker-compose down
docker-compose up -d

Error: "403 Forbidden" หรือ "Unauthorized" เมื่อ push การวิเคราะห์

Token ที่ใช้ expired, revoked หรือไม่มี permission อย่างเพียงพอ

# ตรวจสอบ token ยังใช้ได้
curl -u "admin:SONARQUBE_TOKEN" \
  https://sonarqube.yourdomain.com/api/user_tokens/list

# สร้าง token ใหม่:
# Admin → Security → Users → admin → Generate Tokens

# ใช้ token ใหม่ในคำสั่ง sonar-scanner หรือ Jenkins/GitLab CI

Error: "Quality Gate failed" แม้โปรเจกต์ดูสะอาด

Quality Gate เสต็นดาร์ดมักเข้มงวด ตรวจสอบ condition ที่ fail

# ดู Quality Gate ของโปรเจกต์ใน dashboard
# เปิด Project → Quality Gate → แล้วดู Failed Conditions

# คำแนะนำ: ปรับ gate ให้เหมาะสมกับ maturity ของทีม
# ตัวอย่าง: ใช้ "Sonar Way - Recommended" แทน strict built-in

# เปลี่ยน Quality Gate:
# Project Settings → Quality Gate → Select different gate

SonarScanner ช้า หรือ timeout ในการวิเคราะห์

โปรเจกต์ใหญ่เกินไป SonarQube Server ไม่มี resource เพียงพอ หรือ network latency สูง

# เพิ่ม memory ให้ SonarQube ใน docker-compose.yml
environment:
  SONAR_JAVA_OPTS: "-Xmx2g -Xms1g"

# หรือเพิ่ม timeout ใน sonar-scanner:
sonar-scanner \
  -Dsonar.host.url=... \
  -Dsonar.login=... \
  -Dsonar.ws.timeout=120

# ตรวจสอบ resource ใช้
docker stats sonarqube

# ถ้า CPU/Memory สูง ให้ upgrade VPS หรือทำการวิเคราะห์แบบ incremental
# (วิเคราะห์เฉพาะ changed files แทนทั้งโปรเจกต์)

คำถามที่พบบ่อย

SonarQube Community Edition มีข้อจำกัดอะไร?

Community Edition เป็นฟรีแต่ไม่มี Branch analysis (แยกวิเคราะห์ main, develop, feature branch) ไม่มี Portfolio dashboard สำหรับหลายโปรเจกต์ และไม่มี advanced security features (SAST vulnerability detection) ส่วน Developer, Enterprise Edition มี แต่มีค่าใช้จ่าย สำหรับ VPS ของเล็กๆ ถึงกลาง Community Edition ก็พอ หากต้อง branch analysis ลองใช้ Self-Managed Edition (Community+ จ่ายเท่านั้น)

เก็บผลลัพธ์วิเคราะห์ได้นานแค่ไหน?

SonarQube เก็บประวัติ full analysis ได้ไม่จำกัด ตราบเท่าที่ database พอ แต่สำหรับ cleanup เก่าที่สุด ตั้ง retention policy ใน Admin → Housekeeping — ลบ analysis ที่เก่ากว่าจำนวนวันที่กำหนด (default 30 วัน) เพื่อประหยัด disk space โดย raw data ทั่วไปไม่กิน disk มาก ประมาณ 10-50 MB ต่อโปรเจกต์ต่อสัปดาห์

สามารถวิเคราะห์ code บนสาขา branch เฉพาะได้ไหม?

ใช่ ใช้ parameter `-Dsonar.branch.name=feature-xyz` เมื่อรัน sonar-scanner (ในไฟล์ Community Edition ต้องใช้ Developer+ Edition สำหรับ branch analysis ที่เต็มรูปแบบ) สำหรับ GitLab CI auto-detect branch แล้ว ส่วน Jenkins ต้องเพิ่ม parameter เอง ใน Jenkinsfile: `-Dsonar.branch.name=$GIT_BRANCH` บรรทัด main จะแสดง branch-aware metrics


สำหรับ VPS Singapore รองรับ workload งาน DevOps หนัก ลอง VPS Singapore ของ asiagb — latency ต่ำ uptime 99% เหมาะสำหรับทีม Developer ในไทย ก่อนเลือก VPS ควรตรวจสุขภาพ domain ด้วย dnsxray.com