In this post, I will describe how to install Gitlab with Ansible and Docker. Gitlab has a lot of different components and Docker container permit to manage all of them easily.

github playbook repository

I made a playbook available on github with all files used in this post: gitlab_ansible_docker.

The playbook directory structure is like that:

.
├── gitlab.yml
├── group_vars
│   └── all
├── hosts
└── roles
    └── gitlab
        ├── tasks
        │   └── main.yml
        └── templates
            ├── gitlab.rb.j2
            └── nginx.conf.j2

The hosts file contains the list of hosts where to perform the tasks:

192.168.56.101

The gitlab.yml file will specify what roles to run and to use sudo to become root:

- hosts: all
  become: true
  become_user: root
  roles:
    - docker
    - gitlab

All variables used in the tasks and templates files are defined in group_vars/all.

To run the playbook, the command will be:

ansible-playbook -i hosts gitlab.yml

gitlab directories

The first task will be to create the directories used by the Gitlab installation.

We define variables to specify the configuration, logs and data directory used by Gitlab.

group_vars/all:

gitlab_config_dir: "/srv/gitlab/config"
gitlab_logs_dir: "/srv/gitlab/logs"
gitlab_data_dir: "/srv/gitlab/data"

It’s a simple Ansible task using the file module and a standard loop to process the variables.

roles/gitlab/tasks/main.yml:

- name: Create gitlab directories
  file: path={{ item }} recurse=yes state=directory
  with_items:
    - "{{ gitlab_config_dir }}"
    - "{{ gitlab_logs_dir }}"
    - "{{ gitlab_data_dir }}"

The output when running the playbook should be something like that:

TASK [gitlab : Create gitlab directories] **************************************
ok: [192.168.56.101] => (item=/srv/gitlab/config)
ok: [192.168.56.101] => (item=/srv/gitlab/logs)
ok: [192.168.56.101] => (item=/srv/gitlab/data)

Docker image

task

The docker module is used to download and configure the gitlab image.

Here I use a variable to use the Community Edition image:

gitlab_image: "gitlab/gitlab-ce:latest"

The task itself is pretty easy to read:

- name: Install Gitlab container
  docker:
    name: gitlab
    image: "{{ gitlab_image }}"
    state: started
    ports:
      - "{{ gitlab_ssh_port }}:22"
      - "{{ gitlab_http_port }}:80"
      - "{{ gitlab_https_port }}:443"
    volumes:
      - "{{ gitlab_config_dir }}:/etc/gitlab"
      - "{{ gitlab_logs_dir }}:/var/log/gitlab"
      - "{{ gitlab_data_dir }}:/var/opt/gitlab"
    restart_policy: always

volumes

The volumes section will map the directories created in the previous steps to Gitlab directories inside the container. It’s a standard best practice to not store data inside the container itself.

ports

By default, the gitlab community image publish 3 ports:

  • ssh port can be used with git like transport for the differents projects.
  • http port for gitlab unencrypted web interface access.
  • http port for gitlab web interface access over ssl/tls.

3 variables are defined:

gitlab_ssh_port: "2222"
gitlab_https_port: "127.0.0.1:10443"
gitlab_http_port: "127.0.0.1:10080"

I specify 127.0.0.1 for http and https because I don’t want to make directly accessible Gitlab. It will be put behind a NGINX reverse proxy.

ssh will be made available over port 2222.

gitlab configuration file

The container reconfigure itself at each start by reading the gitlab.rb configuration file.

The most important parameter is external_url. If you don’t want to configure other options you can use the lineinfile module to perform this action:

- name: change gitlab external url
  lineinfile: dest="{{ gitlab_config_dir }}/gitlab.rb" regexp="^# external_url" line="external_url = 'https://{{ gitlab_site }}'" state=present

But you will not be able to send emails and reverse proxy configuration will not work. It’s better to use the template module to replace the configuration file. roles/gitlab/templates/gitlab.rb.j2:

external_url 'https://{{ gitlab_site }}'
gitlab_rails['lfs_enabled'] = true

nginx['listen_port'] = 80
nginx['listen_https'] = false
nginx['proxy_set_headers'] = {
  "X-Forwarded-Proto" => "https",
  "X-Forwarded-Ssl" => "on"
}

gitlab_rails['smtp_enable'] = true
gitlab_rails['smtp_address'] = " {{ gitlab_smtp_server }}"
gitlab_rails['smtp_port'] = 587
gitlab_rails['smtp_user_name'] = "{{ gitlab_smtp_user }}"
gitlab_rails['smtp_password'] = "{{ gitlab_smtp_password }}"
gitlab_rails['smtp_domain'] = "{{ gitlab_smtp_domain }}"
gitlab_rails['smtp_authentication'] = "login"
gitlab_rails['smtp_enable_starttls_auto'] = true
gitlab_rails['smtp_openssl_verify_mode'] = 'peer'
gitlab_rails['gitlab_email_from'] = "{{ gitlab_smtp_user }}"
gitlab_rails['gitlab_email_reply_to'] = "{{ gitlab_smtp_user }}"

This configuration file contains a section dedicated to nginx proxying. Another section define the parameters needed to access an external smtp server.

The variables are defined in group_vars/all:

gitlab_smtp_server: "mail.example.com"
gitlab_smtp_user: "gitlab@example.com"
gitlab_smtp_password: "mypassword"
gitlab_smtp_domain: "example.com"

task

The task itself is:

- name: deploy gitlab configuration
  template: src=gitlab.rb.j2 dest="{{ gitlab_config_dir }}/gitlab.rb" owner=root group=root mode=0644

nginx configuration file

Like said previously I use NGINX as reverse proxy for Gitlab. It allows to setup other security measures like basic authentication.

The file itself is pretty standard:

server {
	listen 443;
	server_name {{ gitlab_site }};
  access_log /var/log/nginx/gitlab_access.log;
  error_log /var/log/nginx/gitlab_error.log;

	index index.php index.html index.htm;
  server_tokens off;

	ssl on;
  ssl_certificate {{ gitlab_fullchain }};
  ssl_certificate_key {{ gitlab_cert_key }};

  ssl_stapling on;
  ssl_stapling_verify on;
  ssl_trusted_certificate {{ gitlab_fullchain }};

  ssl_session_timeout 5m;
  ssl_dhparam /etc/nginx/ssl/dhparam.pem;
  ssl_protocols  TLSv1 TLSv1.1 TLSv1.2;
  ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
  ssl_session_cache shared:SSL:10m;
  ssl_prefer_server_ciphers on;

  location / {
    auth_basic "Restricted";
    auth_basic_user_file htpasswd;
    proxy_set_header        Host $host;
    proxy_set_header        X-Real-IP $remote_addr;
    proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header        X-Forwarded-Proto $scheme;
    proxy_pass              http://{{ gitlab_http_port }}/;
  }
}

I didn’t choose to use the Gitlab ssl port. I don’t see any benefits by keeping ssl between nginx and the container. Everything is on the same container.

The variables specify what certificates to use in NGINX. If you need certificates, you should definitely try Let’s Encrypt.

gitlab_site: "git.example.com"
gitlab_fullchain: "/etc/letsencrypt/live/git.example.com/fullchain.pem"
gitlab_cert_key: "/etc/letsencrypt/live/git.example.com/privkey.pem"

tasks

We upload the configuration file, enable it in NGINX(on ubuntu) and restart NGINX:

- name: deploy gitlab NGINX configuration
  template: src=nginx.conf.j2 dest=/etc/nginx/sites-available/gitlab.conf owner=root group=root mode=0644

- name: Enable gitlab NGINX configuration
  file: src=/etc/nginx/sites-available/gitlab.conf dest=/etc/nginx/sites-enabled/gitlab.conf state=link

- name: Restart NGINX
  service: name=nginx state=restarted

the End

I really like this way of work with Ansible and Docker. I switch time to time from one hardware to another and the time “lost” when creating the playbook is retrieved 10 times when deploying it again on new servers or test systems. And it’s really fun to do. :)