Automated Orchestration of Dockerized Applications via GitLab CI/CD

The architectural integration of GitLab CI/CD with Docker provides a robust framework for achieving continuous deployment, a methodology where code changes are automatically built, tested, and deployed to production environments. This process transforms the software delivery lifecycle from a manual, error-prone sequence of events into a streamlined, automated pipeline. By leveraging GitLab as an open-source collaboration platform, developers gain access to a comprehensive suite of tools that extend far beyond simple code hosting. The platform incorporates issue tracking, package and registry hosting, and Wiki maintenance, all of which orbit the central capability of Continuous Integration (CI) and Continuous Deployment (CD). In a typical production workflow, the objective is to ensure that every commit pushed to a repository triggers a sequence of events: the creation of a Docker image, the pushing of that image to a secure container registry, and the subsequent deployment of that container onto a target server via Secure Shell (SSH). This automation ensures that the latest version of the application is always reflecting the current state of the main branch, thereby reducing the time between the writing of a feature and its availability to the end-user.

Infrastructure Prerequisites and Environment Setup

To establish a functional deployment pipeline, specific hardware and software requirements must be met on both the development and deployment sides. The foundation of this setup requires an active firewall and a user with sudo privileges to manage system-level configurations. For those utilizing Ubuntu, it is imperative to use a current version; specifically, versions 16.04 or below are unsupported and should be upgraded to ensure system stability and security.

The deployment server requires a dedicated user to handle the application lifecycle, which prevents the CI/CD pipeline from needing root access, thereby adhering to the principle of least privilege.

  • Create a dedicated deployment user using the command sudo adduser <project>.
  • Grant the project user permission to manage Docker containers by adding them to the docker group with sudo adduser <project> docker.

The security of the connection between the GitLab Runner and the deployment server is managed through SSH key pairs. This eliminates the need for password-based authentication, which is insecure and incompatible with automated scripts.

  • Switch to the deployment user with su deployer.
  • Generate a 4096-bit RSA key pair using ssh-keygen -b 4096.
  • When prompted for the file location and passphrase, press ENTER to use the default path and leave the passphrase empty.
  • Authorize the key by appending the public key to the authorized keys file using cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys.

GitLab CI/CD Variable Configuration

The automation process requires the GitLab Runner to authenticate with the remote server without human intervention. This is achieved by storing sensitive credentials as environment variables within the GitLab web interface.

  • Navigate to Settings > CI/CD > Variables.
  • Create a variable named SSH_PRIVATE_KEY and paste the content of the private key, which can be retrieved using cat .ssh/id_rsa.
  • To prevent Man-in-the-Middle (MITM) attacks, the server's identity must be verified. Use ssh-keyscan -t rsa -H <deploy.server> to retrieve the SSH fingerprint.
  • Create a second variable named SSH_HOST_KEY and assign it the value of the retrieved fingerprint.

These variables act as a secure vault, ensuring that the private keys are never committed to the version control system, which would be a catastrophic security failure.

Docker Integration and Image Management

The pipeline relies on Docker to package the application into a portable image. This ensures that the environment in which the application is developed is identical to the environment in which it is deployed.

Docker Executor and Runners

To execute CI/CD jobs within Docker containers, the GitLab Runner must be configured with the Docker executor. This allows the pipeline to spin up temporary containers to run scripts, ensuring a clean environment for every job.

  • Register a runner and set its executor to Docker.
  • Specify the required container image in the .gitlab-ci.yml file.
  • Optionally, define sidecar services, such as MySQL, in containers to facilitate integration testing.

Registry Authentication and Credential Helpers

When utilizing private registries, such as those provided by AWS or GitLab, the runner must be authenticated. This can be managed through the DOCKER_AUTH_CONFIG variable or by configuring the helper program in the runner's path.

Configuration Method Implementation Detail Use Case
CI/CD Variable Create DOCKER_AUTH_CONFIG with JSON content General purpose cloud runners
Local File Add JSON to ${GITLAB_RUNNER_HOME}/.docker/config.json Self-managed runners
Credential Helper Install docker-credential-ecr-login in $PATH AWS ECR private images

If a user attempts to pull images from both a private registry and Docker Hub simultaneously, the Docker daemon may fail because it attempts to use the same credentials for all registries. Utilizing specific credential helpers resolves this conflict.

Pipeline Architecture and the .gitlab-ci.yml File

The .gitlab-ci.yml file is the brain of the automation process. Once this file is pushed to the repository, GitLab automatically detects it and initiates the pipeline. The pipeline is structured into stages, typically starting with a "publish" stage and ending with a "deploy" stage.

The Publishing Phase

In the publishing stage, the pipeline performs the following:
- Builds the Docker image based on the Dockerfile present in the source code.
- Pushes the image to the GitLab container registry.

The Deployment Phase

The deployment stage leverages the previously configured SSH keys to connect to the server. It can utilize a template-based approach for orchestration. For instance, a docker-compose.tmpl file can be used to generate a docker-compose.yml file on the deployment server. This allows the pipeline to dynamically inject variables into the compose file before starting the containers.

Validating and Debugging the Deployment

Once the pipeline is triggered, the user must verify that the application has been successfully deployed.

  • Navigate to Build > Pipelines to monitor the status. A successful pipeline is indicated by two green checkmarks.
  • To inspect the logs, click the "passed" button in the status column.
  • The job result page provides the full shell output, which is the primary resource for debugging failures.
  • Check the right sidebar of the job result page to verify the deployment tag and confirm the execution occurred on the correct Deployment Runner.
  • An overview of all historical deployments can be found under Build > Jobs.

The final validation is performed by visiting the server's IP address in a web browser, which should display the deployed static web page.

Advanced Deployment Strategies and Rollbacks

One of the most critical features of GitLab environments is the ability to handle defective deployments. If a new push introduces a bug, the system allows for an immediate rollback to a previous, stable version. This ensures high availability and minimizes downtime.

The current architecture allows for:
- Automatic deployment on every push to the repository.
- Rollback to the first deployment using GitLab environments.
- Shortened development cycles by accelerating the feedback loop between code commit and live deployment.

For those looking to move beyond a simple IP-based access model, the next logical progression is the implementation of a reverse proxy. Using Traefik on Ubuntu 20.04 or 18.04 allows the service to be accessible via a professional domain name and secures the communication channel using HTTPS.

Summary of Technical Workflow

The complete chain of automation can be summarized as follows:

  • Commit and push .gitlab-ci.yml to the remote repository.
  • GitLab detects the file and starts the pipeline.
  • The Runner builds the Docker image.
  • The image is pushed to the GitLab registry.
  • The Runner connects via SSH using the SSH_PRIVATE_KEY.
  • The Runner executes the deployment script on the server.
  • The application is launched via Docker Compose.
  • The user verifies the deployment via browser and GitLab Build logs.

Conclusion

The implementation of a continuous deployment pipeline using GitLab and Docker represents a significant leap in operational efficiency. By removing the manual overhead of server logins, image pulls, and service restarts, organizations can achieve a state of constant delivery. The use of SSH key-based authentication and CI/CD variables ensures that security is maintained without sacrificing automation. The ability to utilize Docker executors and credential helpers allows this system to scale from simple static pages to complex microservices architectures utilizing private registries like AWS ECR. Ultimately, this automated chain reduces the friction of deployment, allowing developers to focus on code quality while the infrastructure ensures that the latest stable version of the application is always live.

Sources

  1. How to Set Up a Continuous Deployment Pipeline with GitLab on Ubuntu
  2. Continuous Deployment with GitLab and Docker Compose
  3. Using Docker Images in GitLab CI/CD

Related Posts