GitLab教程(9): 构建Docker镜像与容器镜像仓库
GitLab提供了内置的容器镜像仓库(Container Registry),可以与CI/CD无缝集成。本文将介绍如何在GitLab中构建和管理Docker镜像。
GitLab Container Registry
# 镜像仓库地址格式
registry.gitlab.com//
registry.gitlab.com///
registry.gitlab.com///:
# 示例
registry.gitlab.com/mygroup/myproject
registry.gitlab.com/mygroup/myproject:latest
registry.gitlab.com/mygroup/myproject/backend:v1.0.0
registry.gitlab.com/mygroup/myproject/frontend:v1.0.0
# 自托管GitLab的地址
registry.example.com/mygroup/myproject:latest
# 查看镜像
# Project > Packages & Registries > Container Registry
登录镜像仓库
# 使用个人访问令牌登录
docker login registry.gitlab.com
Username: your-username
Password: your-personal-access-token
# 输出
Login Succeeded
# 非交互式登录
echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
# 在CI/CD中自动登录(使用预定义变量)
# $CI_REGISTRY = registry.gitlab.com
# $CI_REGISTRY_USER = gitlab-ci-token
# $CI_REGISTRY_PASSWORD = $CI_JOB_TOKEN
# $CI_REGISTRY_IMAGE = registry.gitlab.com/group/project
在CI/CD中构建镜像
使用Docker-in-Docker
# .gitlab-ci.yml
variables:
DOCKER_TLS_CERTDIR: "/certs"
stages:
- build
- push
build-image:
stage: build
image: docker:24
services:
- docker:24-dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
# 执行输出
$ docker login -u gitlab-ci-token -p [MASKED] registry.gitlab.com
Login Succeeded
$ docker build -t registry.gitlab.com/mygroup/myproject:abc123 .
Sending build context to Docker daemon 15.36MB
Step 1/10 : FROM node:18-alpine
---> 7a3c3e7d0e5a
Step 2/10 : WORKDIR /app
...
Successfully built 1a2b3c4d5e6f
Successfully tagged registry.gitlab.com/mygroup/myproject:abc123
$ docker push registry.gitlab.com/mygroup/myproject:abc123
The push refers to repository [registry.gitlab.com/mygroup/myproject]
abc123: Pushed
latest: digest: sha256:xxx size: 1234
使用Kaniko(无需特权模式)
# Kaniko不需要特权模式,更安全
build-image:
stage: build
image:
name: gcr.io/kaniko-project/executor:v1.19.2-debug
entrypoint: [""]
script:
- mkdir -p /kaniko/.docker
- echo "{\"auths\":{\"${CI_REGISTRY}\":{\"auth\":\"$(printf "%s:%s" "${CI_REGISTRY_USER}" "${CI_REGISTRY_PASSWORD}" | base64 | tr -d '\n')\"}}" > /kaniko/.docker/config.json
- |
/kaniko/executor \
--context "${CI_PROJECT_DIR}" \
--dockerfile "${CI_PROJECT_DIR}/Dockerfile" \
--destination "${CI_REGISTRY_IMAGE}:${CI_COMMIT_TAG:-latest}" \
--destination "${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHA}"
# Kaniko构建输出
INFO[0000] Retrieving image manifest node:18-alpine
INFO[0002] Retrieving image manifest node:18-alpine
INFO[0004] Built cross stage deps: map[]
INFO[0004] Retrieving image manifest node:18-alpine
INFO[0006] Executing 0 build triggers
INFO[0006] Building stage 'node:18-alpine' [idx: '0', base-idx: '-1']
INFO[0006] Unpacking rootfs as cmd COPY package*.json ./ requires it.
INFO[0010] WORKDIR /app
INFO[0010] Cmd: workdir
INFO[0010] COPY package*.json ./
INFO[0010] RUN npm ci
...
INFO[0045] Pushing image to registry.gitlab.com/mygroup/myproject:latest
INFO[0050] Pushed registry.gitlab.com/mygroup/myproject:latest
多阶段构建
# Dockerfile 多阶段构建
# 构建阶段
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# 生产阶段
FROM node:18-alpine AS production
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
EXPOSE 3000
CMD ["node", "dist/main.js"]
# .gitlab-ci.yml
build:
stage: build
image: docker:24
services:
- docker:24-dind
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build --target production -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
缓存Docker层
# 使用--cache-from加速构建
build:
stage: build
image: docker:24
services:
- docker:24-dind
variables:
DOCKER_BUILDKIT: 1
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
# 拉取上次构建的镜像作为缓存
- docker pull $CI_REGISTRY_IMAGE:latest || true
# 使用缓存构建
- |
docker build \
--cache-from $CI_REGISTRY_IMAGE:latest \
--tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA \
--tag $CI_REGISTRY_IMAGE:latest \
.
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
- docker push $CI_REGISTRY_IMAGE:latest
# 构建输出(使用缓存)
Step 1/10 : FROM node:18-alpine
---> Using cache
---> 7a3c3e7d0e5a
Step 2/10 : WORKDIR /app
---> Using cache
---> 8b4c5d6e7f8g
...
多架构镜像
# 使用buildx构建多架构镜像
build-multiarch:
stage: build
image: docker:24
services:
- docker:24-dind
variables:
DOCKER_BUILDKIT: 1
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker buildx create --use --name multiarch-builder
- docker buildx inspect --bootstrap
script:
- |
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA \
--tag $CI_REGISTRY_IMAGE:latest \
--push \
.
# 输出
[+] Building 120.5s (18/18) FINISHED
=> [linux/amd64] FROM node:18-alpine
=> [linux/arm64] FROM node:18-alpine
=> ...
=> pushing layers
=> pushing manifest for registry.gitlab.com/mygroup/myproject:latest
镜像标签策略
# 完整的标签策略示例
variables:
DOCKER_TLS_CERTDIR: "/certs"
build:
stage: build
image: docker:24
services:
- docker:24-dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
# 基础镜像名
- IMAGE_NAME="$CI_REGISTRY_IMAGE"
# 构建镜像
- docker build -t $IMAGE_NAME:$CI_COMMIT_SHA .
# 推送commit SHA标签
- docker push $IMAGE_NAME:$CI_COMMIT_SHA
# 如果是tag,推送版本标签
- |
if [ -n "$CI_COMMIT_TAG" ]; then
docker tag $IMAGE_NAME:$CI_COMMIT_SHA $IMAGE_NAME:$CI_COMMIT_TAG
docker push $IMAGE_NAME:$CI_COMMIT_TAG
fi
# 如果是main分支,推送latest
- |
if [ "$CI_COMMIT_BRANCH" == "main" ]; then
docker tag $IMAGE_NAME:$CI_COMMIT_SHA $IMAGE_NAME:latest
docker push $IMAGE_NAME:latest
fi
# 如果是develop分支,推送dev标签
- |
if [ "$CI_COMMIT_BRANCH" == "develop" ]; then
docker tag $IMAGE_NAME:$CI_COMMIT_SHA $IMAGE_NAME:dev
docker push $IMAGE_NAME:dev
fi
# 结果
# main分支: :abc123, :latest
# develop分支: :abc123, :dev
# v1.0.0 tag: :abc123, :v1.0.0
镜像扫描
# GitLab内置容器扫描
include:
- template: Security/Container-Scanning.gitlab-ci.yml
variables:
CS_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
# 或使用Trivy
container-scan:
stage: test
image:
name: aquasec/trivy:latest
entrypoint: [""]
script:
- trivy image --exit-code 1 --severity HIGH,CRITICAL $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
allow_failure: true
# Trivy扫描输出
registry.gitlab.com/mygroup/myproject:abc123 (alpine 3.18.4)
Total: 2 (HIGH: 1, CRITICAL: 1)
┌──────────────┬────────────────┬──────────┬────────────────┐
│ Library │ Vulnerability │ Severity │ Fixed Version │
├──────────────┼────────────────┼──────────┼────────────────┤
│ openssl │ CVE-2024-1234 │ CRITICAL │ 3.1.4-r1 │
│ curl │ CVE-2024-5678 │ HIGH │ 8.4.0-r0 │
└──────────────┴────────────────┴──────────┴────────────────┘
清理镜像
# 配置清理策略
# Project > Settings > Packages & Registries > Container Registry
# 清理规则示例
- 保留最近10个标签
- 删除超过30天的未标记镜像
- 保护带有语义版本的标签 (v*.*.*)
# 使用API清理
curl --request DELETE \
--header "PRIVATE-TOKEN: your-token" \
"https://gitlab.example.com/api/v4/projects/42/registry/repositories/1/tags/old-tag"
# CI/CD中清理旧镜像
cleanup:
stage: cleanup
image: alpine:latest
script:
- apk add --no-cache curl jq
- |
# 获取所有标签
TAGS=$(curl -s --header "PRIVATE-TOKEN: $REGISTRY_TOKEN" \
"$CI_API_V4_URL/projects/$CI_PROJECT_ID/registry/repositories/1/tags" | jq -r '.[].name')
# 删除旧标签(保留最近10个)
echo "$TAGS" | tail -n +11 | while read tag; do
echo "Deleting tag: $tag"
curl --request DELETE \
--header "PRIVATE-TOKEN: $REGISTRY_TOKEN" \
"$CI_API_V4_URL/projects/$CI_PROJECT_ID/registry/repositories/1/tags/$tag"
done
only:
- schedules
完整示例
stages:
- build
- scan
- push
- deploy
variables:
DOCKER_TLS_CERTDIR: "/certs"
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
build:
stage: build
image: docker:24
services:
- docker:24-dind
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker pull $CI_REGISTRY_IMAGE:latest || true
- docker build --cache-from $CI_REGISTRY_IMAGE:latest -t $IMAGE_TAG .
- docker push $IMAGE_TAG
scan:
stage: scan
image:
name: aquasec/trivy:latest
entrypoint: [""]
script:
- trivy image --exit-code 0 --severity HIGH,CRITICAL $IMAGE_TAG
allow_failure: true
push-latest:
stage: push
image: docker:24
services:
- docker:24-dind
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker pull $IMAGE_TAG
- docker tag $IMAGE_TAG $CI_REGISTRY_IMAGE:latest
- docker push $CI_REGISTRY_IMAGE:latest
rules:
- if: $CI_COMMIT_BRANCH == "main"
deploy:
stage: deploy
image: bitnami/kubectl:latest
script:
- kubectl set image deployment/myapp myapp=$IMAGE_TAG
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
总结
本文介绍了GitLab Container Registry的使用方法,包括镜像构建、推送、扫描和清理。内置的镜像仓库与CI/CD无缝集成,简化了容器化应用的开发流程。
下一篇我们将学习GitLab与Kubernetes的集成。
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。







