Docker and GitLab CI for Containerized Development Workflows
In the modern software development lifecycle, Continuous Integration (CI) and Continuous Deployment (CD) are core mechanisms for ensuring code quality and improving delivery efficiency. Introducing Docker containerization technology into this process resolves the traditional issue of inconsistent local development environments, often summarized as "it works on my machine." This article explores in depth how to leverage GitLab CI to achieve automated building, testing, and pushing based on Docker, constructing a complete and reliable CI/CD pipeline.
Before writing the .gitlab-ci.yml configuration file, we need to ensure that the development environment possesses basic containerization capabilities. First, confirm that Docker Engine and related tools (such as docker-compose) are installed locally and can start containers normally. Second, ensure that the GitLab repository contains necessary build scripts and dependency files.
A standard CI/CD project structure typically includes the following key directories:
Dockerfile: The core file defining image build instructions, specifying the base image, dependency installation, and application deployment steps.docker-compose.yml: A configuration file used to orchestrate multi-container services (such as databases, Nginx, and applications)..gitlab-ci.yml: The GitLab CI/CD automation configuration script that defines the various jobs within the pipeline.
Writing Dockerfile to Define Image Build Specifications
The Dockerfile serves as the blueprint for containerized applications; its content directly determines the purity and consistency of the final runtime environment. When writing it, principles such as "minimizing base images" and "layered build optimization" should be followed.
Taking a Node.js backend service as an example, an efficient Dockerfile may include the following steps:
- Select a lightweight base image (e.g.,
node:18-alpine). - Install runtime libraries required for the production environment (e.g.,
npm). - Copy project code and execute
npm installto optimize dependency caching. - Expose application ports for external access.
FROM node:18-alpine
WORKDIR /app
# Copy package.json and install dependencies, utilizing Docker cache mechanism to accelerate build
COPY package*.json ./
RUN npm ci --only=production
# Copy source code
COPY . .
# Expose port
EXPOSE 3000
# Start application
CMD ["node", "server.js"]
Through this layered structure, Docker can reuse each layer by only changing the modified parts, significantly reducing build time and image size.
Configuring GitLab CI Pipeline Jobs
The .gitlab-ci.yml file acts as the bridge connecting code commits to automated execution. We need to define clear variables, scripts, and dependencies to ensure that a complete build process is triggered every time code is pushed to the main branch (such as main or master).
A typical pipeline usually includes the following core stages:
- Lint & Test: Perform static code analysis and unit tests before building the image to ensure correct logic and compliance with standards.
- Build Image: Build the final container image using Docker CLI.
- Push Image: Push the built image to GitLab Registry or an external container registry (such as Harbor).
Here is a reference configuration example for .gitlab-ci.yml:
variables:
DOCKER_IMAGE_NAME: my-app-service
REGISTRY_URL: "gitlab.com/my-group/my-project/"
stages:
- lint
- test
- build
- push
lint:
stage: lint
script:
- npm run lint
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
test:
stage: test
script:
- npm run test
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
build:
stage: build
image: docker:24.0-dind
services:
- docker:24.0-dind
before_script:
- docker login -u ${CI_USER} -p ${CI_REGISTRY_PASSWORD} ${REGISTRY_URL}
script:
- docker build -t ${DOCKER_IMAGE_NAME}:${CI_COMMIT_SHA} .
- docker tag ${DOCKER_IMAGE_NAME}:${CI_COMMIT_SHA} ${DOCKER_IMAGE_NAME}:latest
rules:
- if: $CI_COMMIT_BRANCH == "main" || $CI_COMMIT_TAG
push:
stage: push
image: docker:24.0-dind
services:
- docker:24.0-dind
script:
- docker push ${DOCKER_IMAGE_NAME}:${CI_COMMIT_SHA}
- docker push ${DOCKER_IMAGE_NAME}:latest
rules:
- if: $CI_COMMIT_BRANCH == "main" || $CI_COMMIT_TAG
In this configuration, we use the docker:24.0-dind (Docker-in-Docker) mode, which allows the CI environment to build images without installing Docker CLI on the host; it simply relies on the Docker daemon provided by GitLab Runner. Additionally, using $CI_COMMIT_SHA as the image tag ensures precise correspondence between versions and code commits.
Executing Automated Deployment and Verification
Once the image is successfully pushed to the repository, subsequent deployment stages can be automatically triggered. For production environments, it is generally recommended to combine docker-compose for orchestration deployment to manage databases, caching middleware, and application services uniformly.
GitLab CI can invoke external scripts or APIs through deploy jobs to pull the latest image and start containers. For example, in a Kubernetes environment, Helm Charts can be used; while on simple Linux servers, shell scripts can be written directly to execute docker-compose up -d. Furthermore, health check mechanisms should be added to ensure that the job is marked as successful only after the service starts in an available state.
Summary and Best Practices
By combining Docker with GitLab CI, developers can establish a standardized, reusable automated delivery system. This process not only eliminates risks caused by environmental differences but also significantly shortens the cycle from code merge to production deployment. In practical applications, it is recommended to continuously optimize the Dockerfile to reduce image size, utilize GitLab's caching features to accelerate dependency pulling, and strictly follow branch protection policies to ensure pipelines execute only on trusted code changes. As the project scales, further integration of automated test reports, security scanning (such as Trivy), and canary release mechanisms can be implemented to build a more robust enterprise-grade CI/CD platform.