Operations 32 min read

How to Build a Lightweight CI/CD Service Automation with DevOps and Docker

This article outlines a comprehensive, lightweight CI/CD automation solution built on DevOps principles, detailing the evolution from traditional deployment to containerized workflows, the design of a multi‑project, plug‑in task system, and practical implementation steps using GitLab CI, Docker, and Go.

Inke Technology
Inke Technology
Inke Technology
How to Build a Lightweight CI/CD Service Automation with DevOps and Docker

1. Introduction and Background

1.1 Evolution of Development Models

(1) Traditional Development Model

In the traditional model, development, operations, and physical machines are tightly coupled; after development, operations deploy the project to a single physical machine.

Because services depend heavily on the physical machine environment, swapping machines requires reinstalling dependencies, which is cumbersome.

(2) The Container Revolution

Virtual machines were introduced to solve this, but were quickly replaced by lightweight OS container products like Docker.

Docker allows packaging service dependencies into an image and running them in containers, decoupling services from physical machines. Developers can script operations in a Dockerfile, breaking down the Dev‑Ops barrier and reducing deployment cost.

(3) Agile Development Innovation

Agile splits product delivery into short cycles, iterating quickly to meet market needs and reduce risk.

(4) DevOps Cultural Movement

Container, agile, and CI/CD technologies fostered the DevOps movement, emphasizing communication among development, testing, and operations. More teams now invest in DevOps, using CI/CD for automated, high‑quality, and standardized development.

2. Characteristics of the Evolution

Development, testing, and operations are no longer isolated; they are increasingly intertwined, driving automation in the development process.

3. Why Write This Article

Current work faces efficiency and quality issues. Existing CI/CD mechanisms are costly and hard to extend, prompting a DevOps‑driven, CI‑based server automation solution that closes the loop for development, testing, deployment, monitoring, and delivery.

4. What This Article Covers

It focuses on CI/CD as the core, explaining its benefits, why a custom CI server automation is needed, and how to design it.

5. Reading Precautions

CI/CD will be referred to as CI in the text.

Typos or misunderstandings are welcome for correction.

2. Problem Status

1) Fast Project Iteration

Agile shortens development cycles, compressing requirements, testing, and other phases, leading to rapid iteration.

Example: a feature may be released without testing after product acceptance.

2) Heavy Third‑Party Tools

Many third‑party tools are bulky, offering many features but requiring unnecessary resources, unsuitable for fast‑iteration projects.

Example: API documentation generators often need a private server.

3) Tight Testing Schedule

Testing time is limited, especially with multi‑platform testing and bug verification, making it hard to ensure quality.

Example: QA discovers issues late because developers omitted unit tests.

4) Slow Requirement Fulfilment

Even with fast agile cycles, meeting QA, Ops, or SRE requests can lag behind iteration speed.

Example 1: Automated test support requires QA scheduling. Example 2: Configuration checks for panic prevention need SRE scheduling.

3. Solution

Without a complete mechanism, handling fast agile iteration is difficult and quality suffers. Applying DevOps and CI, we built a comprehensive server automation that breaks departmental silos and meets speed and quality demands.

4. Practice

1) Design Scheme

(1) Lightweight Design

The primary goal is lightweight—removing unnecessary details while meeting requirements with minimal cost and rapid delivery.

Only rely on existing GitLab Runner machines; no extra servers.

No extra backend management system; use GitLab repository and store API docs in /apidoc.md and Wiki.

Third‑party caches are kept under 50 MB, most tools stay below 10 MB.

(2) Multi‑Project Sharing

In micro‑service architecture, each codebase has its own CI scripts, causing large coordination overhead when any CI changes. We moved CI logic to a shared CommonCI repository and launch it via start.sh .

(3) Plug‑in Tasks

All tasks are plug‑in, extensible, and fully controllable, enabling complex task orchestration.

(4) Third‑Party Cache

GitLab CI’s built‑in cache and artifacts have limitations, so we built a custom cache library to store compiled binaries and speed up CI.

2) Technologies and Ideas

(1) Technologies Used

GitLab stack: GitLab CI and API for pipeline automation.

Shell scripting: for task plug‑in and orchestration.

Go: for configuration checks, dependency checks, API doc generation, etc.

Container stack: Docker for future containerized service management.

(2) DevOps Thought

Why build server automation? What does it achieve? How to implement? All driven by DevOps.

3) Overall Architecture

The CI‑based server automation consists of three layers:

Code Repository Layer – business and common repositories.

Runner Layer – GitLab Runner servers that execute CI.

CI Automation Layer – abstracted plug‑in tasks forming the pipeline.

4) Code Repository Layer

Besides business code, a common CommonCI repository provides shared CI scripts:

/.gitlab – common methods and CI task directories.

/start.sh – launches CI by executing all *.sh scripts under /.gitlab .

5) CI Automation Layer

(1) Pre‑Installation

Pre‑install scripts are placed in .gitlab/pre_install.sh .

<code>echo -e "------------ 开始运行预安装 ------------"
EveryStageCommonOperation
# check and install inkedep tool
if ! which "inkedep" >/dev/null 2>&1; then
  echo -e "inkedep不存在, 正在安装 ..."
  go get git.inke.cn/BackendPlatform/inkedep && cd $GOPATH/src/git.inke.cn/BackendPlatform/inkedep && go install && inkedep ver && cd $currentDir
else
  echo -e "inkedep已安装"
fi
# copy project to $PROJECT_PATH
mkdir -p $PROJECT_PATH && cp -r ./ $PROJECT_PATH && cd $PROJECT_PATH
# build project
cd $APP_PATH && inkedep save && inkedep build && cd $PROJECT_PATH
# fetch other dependencies
inkedep get code.inke.cn/lingxi/server/metis/metis.common.tools
echo -e "------------ 预安装运行结束 ------------"
</code>

(2) Code Check

We use golangci-lint to catch most code errors.

<code>echo -e "------------ 开始运行代码检查 ------------"
if [ -z "$CHECK_CODE_SWITCH" ] || [ "$CHECK_CODE_SWITCH" = "off" ]; then
  echo -e "因关闭代码检查, 已跳过check_code.sh"
  exit 0
fi
EveryStageCommonOperation
# install golangci-lint if missing
if ! which "$GOPATH/bin/golangci-lint" >/dev/null 2>&1; then
  echo -e "golangci-lint不存在, 正在安装 ..."
  curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.46.2
else
  echo -e "golangci-lint已安装"
fi
# run lint
$(go env GOPATH)/bin/golangci-lint run --timeout=10m
if [ $? -ne 0 ]; then
  FAILURE_REASON="代码检查失败"
  SendFailureNotice
  exit 1
fi
echo -e "------------ 代码检查运行结束 ------------"
</code>

(3) Config Check

A lightweight config‑check tool compares test and online configs to prevent missing configurations that cause panics.

<code>echo -e "------------ 开始运行配置检查 ------------"
if [ -z "$UNIT_TEST_SWITCH" ] || [ "$UNIT_TEST_SWITCH" = "off" ]; then
  echo -e "因关闭配置检查, 已跳过config_check.sh"
  exit 0
fi
# install cdiff if missing
if ! which "./cdiff" >/dev/null 2>&1; then
  echo -e "cdiff工具不存在,正在安装 ..."
  go get code.inke.cn/ConfigDiff && cd $GOPATH/src/code.inke.cn/ConfigDiff && go build -o cdiff main.go
else
  echo -e "cdiff工具已安装"
fi
result=$(./cdiff --testConfigPath=$TEST_CONFIG_FILE --onlineConfigPath=$ONLINE_CONFIG_FILE | grep 'Unfortunately!  There are some difference as follows')
if [ -n "$result" ]; then
  ./cdiff --testConfigPath=$TEST_CONFIG_FILE --onlineConfigPath=$ONLINE_CONFIG_FILE
  FAILURE_REASON="配置检查失败"
  SendFailureNotice
  exit 1
fi
echo -e "------------ 配置检查运行结束 ------------"
</code>

(4) Dependency Check

Parses Godeps.json to find wrong dependencies across projects.

<code>echo -e "------------ 开始运行依赖检查 ------------"
if [ -z "$UNIT_TEST_SWITCH" ] || [ "$UNIT_TEST_SWITCH" = "off" ]; then
  echo -e "因关闭依赖检查, 已跳过godep_check.sh"
  exit 0
fi
# install checkdep if missing
if ! which "./checkdep" >/dev/null 2>&1; then
  echo -e "checkdep工具不存在,正在安装中 ..."
  go get code.inke.cn/checkGodeps && cd $GOPATH/src/code.inke.cn/checkGodeps && go build -o checkdep main.go
else
  echo -e "checkdep工具已安装"
fi
result=$(./checkdep --goDepsFile=$GODEPS_FILE --pattern $GODEPS_PATTERN | grep '==import wrong project==')
if [ -n "$result" ]; then
  ./checkdep --goDepsFile=$GODEPS_FILE --pattern $GODEPS_PATTERN
  FAILURE_REASON="依赖检查失败"
  SendFailureNotice
  exit 1
fi
echo -e "------------ 依赖检查运行结束 ------------"
</code>

(5) Unit Test

Extensive unit tests are written and integrated into CI to avoid hidden bugs.

<code>echo -e "------------ 开始运行单元测试 ------------"
if [ -z "$UNIT_TEST_SWITCH" ] || [ "$UNIT_TEST_SWITCH" = "off" ]; then
  echo -e "因关闭单元测试, 已跳过unit_test.sh"
  exit 0
fi
EveryStageCommonOperation
echo "1. 执行单元测试"
eval $UNIT_TEST_TRIGGER_CMD
if [ $? -ne 0 ]; then
  FAILURE_REASON="单元测试失败"
  SendFailureNotice
  exit 1
fi
echo -e "------------ 单元测试运行结束 ------------"
</code>

(6) Local Build

Builds the project locally, packaging and uploading artifacts.

<code>cd $PROJECT_PATH
currentDir=$(pwd)
. $currentDir/.gitlab/include/function.sh
if [ -z "$LOCAL_BUILD_SWITCH" ] || [ "$LOCAL_BUILD_SWITCH" = "off" ]; then
  echo -e "因关闭本地构建, 已跳过local_build.sh"
  exit 0
fi
EveryStageCommonOperation
eval $LOCAL_BUILD_TRIGGER_CMD
if [ $? -ne 0 ]; then
  FAILURE_REASON="本地构建失败"
  SendFailureNotice
  exit 1
else
  echo -e "本地构建成功"
fi
echo -e "------------ 本地构建运行结束 ------------"
</code>

(7) Health Check (Startup Self‑Check)

After deployment, the service is started and health‑checked repeatedly until success.

<code>if [ -z "$HEALTH_CHECK_SWITCH" ] || [ "$HEALTH_CHECK_SWITCH" = "off" ]; then
  echo -e "因关闭启动自检, 已跳过health_check.sh"
  exit 0
fi
i=0
while true; do
  ((i++))
  res=$(eval $HEALTH_CHECK_TRIGGER_CMD)
  if [ "$res" = "$HEALTH_CHECK_SUCCESS" ]; then
    echo -e "健康检查成功"
    break
  fi
  if [ $i -ge 100 ]; then
    FAILURE_REASON="启动自检失败: $res"
    SendFailureNotice
    exit 1
  fi
  sleep 1
done
echo -e "------------ 启动自检运行结束 ------------"
</code>

(8) API Documentation Generation

A lightweight Go tool generates API docs from models, storing them in GitLab Wiki without a heavy server.

<code>echo -e "------------ 开始运行接口文档自动生成 ------------"
if [ -z "$APIDOC_SWITCH" ] || [ "$APIDOC_SWITCH" = "off" ]; then
  echo -e "因关闭文档自动生成, 已跳过apidoc_gen.sh"
  exit 0
fi
EveryStageCommonOperation
eval $APIDOC_TRIGGER_CMD && cd $currentDir
content=$(cat $APIDOC_FILE)
if [ -z "$content" ]; then
  echo -e "接口文档内容为空或生成失败"
  FAILURE_REASON="接口文档自动生成失败"
  SendFailureNotice
  exit 1
fi
curl --request PUT --data "format=markdown&content=$content&title=APIDoc" \
  --header "PRIVATE-TOKEN: $GITLAB_API_TOKEN" \
  "https://code.inke.cn/api/v4/projects/$PROJECT_ID/wikis/APIDoc"
echo -e "------------ 接口文档自动生成运行结束 ------------"
</code>

(9) API Automated Testing

Go‑based API test suite runs after deployment to ensure end‑to‑end functionality.

<code>if [ -z "$API_TEST_SWITCH" ] || [ "$API_TEST_SWITCH" = "off" ]; then
  echo -e "因关闭接口自动化测试, 已跳过api_test.sh"
  exit 0
fi
EveryStageCommonOperation
eval $API_TEST_TRIGGER_CMD
if [ $? -ne 0 ]; then
  FAILURE_REASON="接口自动化测试失败"
  SendFailureNotice
  exit 1
else
  echo -e "接口自动化测试成功"
fi
echo -e "------------ 接口自动化测试运行结束 ------------"
</code>

(10) Adding/Removing Tasks

To add a new task, place its script (e.g., auto_deploy.sh ) and reference it in start.sh .

6) Project Integration

Previously, CI integration required complex .gitlab-ci.yml scripts. To lower entry cost, we provide a minimal template that only needs a simple configuration file.

<code>image: golang:1.17
variables:
  GITLAB_HOST: ""
  GITLAB_API_TOKEN: ""
  PROJECT_NAME: "apimock-example"
  PROJECT_ID: 48845
  DING_KEYWORD: "apimock-example"
  DING_ACCESS_TOKEN: ""
  DING_NOTICE_SWITCH: "off"
  CHECK_CODE_SWITCH: "on"
  UNIT_TEST_TRIGGER_CMD: "cd mock && go test -v . && cd .. && cd service && go test -v . && cd .."
  UNIT_TEST_SWITCH: "on"
  APIDOC_TRIGGER_CMD: "cd mock && go test -v . && cd .."
  APIDOC_FILE: "apidoc.md"
  APIDOC_SWITCH: "off"
  LOCAL_BUILD_TRIGGER_CMD: "go mod download && go build -o project && nohup ./project &"
  LOCAL_BUILD_SWITCH: "on"
  HEALTH_CHECK_TRIGGER_CMD: "curl -X GET 127.0.0.1:8000/ping"
  HEALTH_CHECK_SUCCESS: "ok"
  HEALTH_CHECK_SWITCH: "on"
before_script:
  - echo '====== CIManager Start Running ======'
after_script:
  - echo '====== CIManager Stopped Successfully ======'
stages:
  - CIManager
CIManager:
  stage: CIManager
  script:
    - git clone -b testing https://github.com/wgrape/CIManager.git ; cp -an ./CIManager/. ./ ; rm -rf ./CIManager ; bash start.sh
</code>

7) Cache‑Driven Performance Boost

Third‑party cache reduced CI runtime from 5‑10 minutes to about 1 minute, a five‑fold speedup.

5. Goals

After practice, we set two main goals:

1) Stability

Avoid missing configurations that cause panics.

Prevent uninitialized resources.

Ensure code changes don’t introduce regressions.

Catch low‑level errors before deployment.

2) Efficiency

Eliminate testing bottlenecks.

Improve front‑end/back‑end coordination via reliable API docs.

6. Review and Summary

We reviewed the need for a CI‑based server automation in fast agile cycles, the DevOps‑driven solution, lightweight design, and how we tackled multi‑project sharing, speed, and extensibility. The presented approach offers a practical, low‑cost way to boost development speed and quality.

DockerCI/CDmicroservicesautomationDevOpsGitLab
Inke Technology
Written by

Inke Technology

Official account of Inke Technology

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.