Introduction

After transitioning services to Go language, the first problem we encountered was how to deploy Go project binaries. After overcoming many challenges, we’ve summarized the following deployment methods:

  • Directly copying binary executables to the target server, then managing service processes through Shell scripts
  • Directly copying binary executables to the target server, using predefined Service configuration files to let Systemd manage service processes
  • Using Docker Images, managing service processes on a single server with Docker CLI
  • Using Docker Images, implementing canary releases through container orchestration and scheduling platforms

The advantage of the first three methods is that they don’t have many requirements for infrastructure, but the disadvantages are also obvious - it’s difficult to achieve code deployment without user awareness unless third-party service registration and discovery infrastructure like Consul or ETCD is integrated into the project code.

To smoothly transition to container orchestration and scheduling, the first thing we need to do is change from traditional binary distribution to Docker Image distribution, which requires a Docker Container Registry.

For teams using Self-Hosted Gitlab for code management, Gitlab Container Registry is a good choice as it’s already built into Gitlab and only needs to be enabled through configuration.

Configuration

The Gitlab service configuration directory structure is as follows:

tree -a -I .git
.
├── .env
├── .gitignore
├── README.md
└── docker-compose.yml

1 directory, 5 files

The .env configuration is as follows:

# Gitlab external access url
EXTERNAL_URL=https://gitlab.local

# Gitlab Pages external access url
PAGES_EXTERNAL_URL=https://pages.local

# Gitlab Container Registry external access url
REGISTRY_EXTERNAL_URL=https://registry.local

# The maximum size of the log before it is rolled. A positive integer plus a modifier representing the unit of measure (k, m, or g). Defaults to 20m.
GITLAB_CONTAINER_LOG_MAX_SIZE=1024m

# SSL Certificates
# SSL_CERTIFICATE=/etc/gitlab/ssl/fullchain.cer
# SSL_CERTIFICATE_KEY=/etc/gitlab/ssl/fullchain.key

# Email configuration
EMAIL_ENABLED=true
[email protected]
EMAIL_DISPLAY_NAME=Gitlab
[email protected]

# SMTP configuration. Related settings for other service providers please refer to https://docs.gitlab.com/omnibus/settings/smtp.html#example-configurations
SMTP_ENABLE=true
SMTP_ADDRESS=smtp.example.com
SMTP_PORT=465
[email protected]
[email protected]
SMTP_DOMAIN=smtp.example.com
SMTP_AUTHENTICATION=login
SMTP_ENABLE_STARTTLS_AUTO=false
SMTP_TLS=true

The docker-compose.yaml configuration is as follows:

services:
  gitlab:
    image: 'gitlab/gitlab-ee:latest'
    restart: no
    logging:
      options:
        max-size: ${GITLAB_CONTAINER_LOG_MAX_SIZE}
    hostname: gitlab
    container_name: gitlab
    networks:
      - traefik
    labels:
      - traefik.enable=true

      - traefik.http.routers.pages.tls=true
      - traefik.http.routers.pages.rule=Host(`pages.local`)
      - traefik.http.routers.pages.service=pages
      - traefik.http.routers.pages.entrypoints=http,https

      - traefik.http.routers.gitlab.tls=true
      - traefik.http.routers.gitlab.rule=Host(`gitlab.local`)
      - traefik.http.routers.gitlab.service=gitlab
      - traefik.http.routers.gitlab.entrypoints=http,https

      - traefik.http.routers.registry.tls=true
      - traefik.http.routers.registry.rule=Host(`registry.local`)
      - traefik.http.routers.registry.service=registry
      - traefik.http.routers.registry.entrypoints=http,https

      - traefik.http.services.pages.loadbalancer.server.port=80
      - traefik.http.services.gitlab.loadbalancer.server.port=80
      - traefik.http.services.registry.loadbalancer.server.port=5100

    environment:
      TZ: Asia/Shanghai
      GITLAB_LOG_LEVEL: error
      GITLAB_OMNIBUS_CONFIG: |
        external_url '$EXTERNAL_URL';
        nginx['enable'] = true;
        nginx['listen_port'] = 80
        nginx['listen_https'] = false
        nginx['redirect_http_to_https'] = true
        nginx['client_max_body_size'] = '1024m';
        nginx['proxy_set_headers'] = {
          "Host" => "$$http_host",
          "Upgrade" => "$$http_upgrade",
          "X-Real-IP" => "$$remote_addr",
          "Connection" => "Upgrade",
          "X-Forwarded-For" => "$$proxy_add_x_forwarded_for",
          "X-Forwarded-Ssl" => "on",
          "X-Forwarded-Proto" => "https"
        };

        registry['enable'] = true
        registry_external_url '$REGISTRY_EXTERNAL_URL'
        registry_nginx['listen_port'] = 5100
        registry_nginx['listen_https'] = false
        registry_nginx['redirect_http_to_https'] = true
        registry_nginx['proxy_set_headers'] = {
          "Host" => "$$http_host",
          "X-Real-IP" => "$$remote_addr",
          "X-Forwarded-For" => "$$proxy_add_x_forwarded_for",
          "X-Forwarded-Ssl" => "on",
          "X-Forwarded-Proto" => "https"
        }

        pages_external_url '$PAGES_EXTERNAL_URL';
        gitlab_pages['enable'] = true;
        gitlab_pages['listen_https'] = false;
        gitlab_pages['inplace_chroot'] = true;
        gitlab_pages['access_control'] = true;
        pages_nginx['enable'] = true;
        pages_nginx['listen_port'] = 80
        pages_nginx['listen_https'] = false
        pages_nginx['redirect_http_to_https'] = true

        gitlab_rails['time_zone'] = 'Asia/Shanghai';
        gitlab_rails['lfs_enabled'] = true;
        gitlab_rails['gitlab_email_enabled'] = true;
        gitlab_rails['gitlab_email_from'] = '$EMAIL_FROM';
        gitlab_rails['gitlab_email_display_name'] = '$EMAIL_DISPLAY_NAME';
        gitlab_rails['gitlab_email_reply_to'] = '$EMAIL_REPLY_TO';
        gitlab_rails['smtp_enable'] = $SMTP_ENABLE;
        gitlab_rails['smtp_address'] = '$SMTP_ADDRESS';
        gitlab_rails['smtp_port'] = $SMTP_PORT;
        gitlab_rails['smtp_user_name'] = '$SMTP_USER_NAME';
        gitlab_rails['smtp_password'] = '$SMTP_PASSWORD';
        gitlab_rails['smtp_domain'] = '$SMTP_DOMAIN';
        gitlab_rails['smtp_authentication'] = '$SMTP_AUTHENTICATION';
        gitlab_rails['smtp_enable_starttls_auto'] = $SMTP_ENABLE_STARTTLS_AUTO;
        gitlab_rails['smtp_tls'] = $SMTP_TLS;
    ports:
      - "0.0.0.0:22:22"
    volumes:
      - gitlab-conf:/etc/gitlab
      - gitlab-logs:/var/log/gitlab
      - gitlab-data:/var/opt/gitlab

volumes:
  gitlab-conf:
    external: true
  gitlab-logs:
    external: true
  gitlab-data:
    external: true

networks:
  traefik:
    external: true
  • Lines 12-32 labels are used for registering reverse proxies with Traefik.
  • Lines 54-64 contain the configuration for enabling Gitlab Container Registry and configuring Nginx reverse proxy.

All domains I set up locally use the .local TLD.

Starting the Service

docker compose up -d

Testing

Access the Container Registry page in a Gitlab project:

OrbStack Config

Login

docker login registry.local
Username: [email protected]
Password:
Login Succeeded

Note: If the domain is not publicly resolvable, you need to set up DNS or configure the Docker Daemon Proxy to log in normally! Replace registry.local with your own domain.

OrbStack Config

Push Image

Here I’ll directly tag Traefik’s official image and push it to the Gitlab Container Registry:

docker tag traefik:latest registry.local/devops/traefik:latest
docker push registry.local/devops/traefik:latest
The push refers to repository [registry.local/devops/traefik]
ef79cc9bf4db: Pushed
684de1f4a5df: Pushed
7f5495899e4d: Pushed
5f4d9fc4d98d: Pushed
latest: digest: sha256:4d7c60c1d882dafc8f0148231d836f7d1e2fef0d732daf1cd16f37d711afe689 size: 1157

Then let’s look at the Container Registry in the project, and we can see the image we just pushed:

OrbStack Config

Summary

This is just the first step in service containerization. To better deploy services, we need more powerful orchestration and scheduling systems!

I hope this is helpful, Happy hacking…