Site Build Part One - Docker and NGINX

I’ve had a Linode server sitting around doing nothing for almost a year, and I had an urge to do something with the $5 a month worth of compute.

All of this is a gigantic waste of team for learning’s sake. This could be done with a lot less time and money just using GitHub Pages and Actions.

The Initial Vision

  1. Server via Ansible. I wanted to be able to build this from a clean Linode instance without accessing the server.
  2. Automated site build and deploys in Jenkins triggered by a GitHub webhook.

That’s it. This is the first in a short series covering the build.

Initial Server Provisioning

I terminated my existing Linode instance and spun up a fresh one using the CentOS7 image. Now I was ready to start provisioning the basic services with Ansible.

The first goal was getting NGINX and Docker installed. I opted for running NGINX directly on the server for serving static content, like this site, and as a proxy for any containerized services. The first step was creating a clean ansible project with a couple roles.

common.yml

- name: Install basic services
  hosts: all
  remote_user: root
  roles:
    - pmclain.NGINX
    - pmclain.DOCKER

The above play will fail until we create the roles, so we do that next.

NGINX Role

Here’s an overview of what this task is doing:

roles/pmclain.NGINX/tasks/main.yml

- name: Install epel-release
  yum:
    name: epel-release
    state: present

- name: Install nginx
  yum:
    name: nginx
    state: present

- name: Create letsencrypt directory
  file:
    name: /etc/letsencrypt
    state: directory

- name: Add default host
  template:
    src: templates/etc/nginx/nginx.conf
    dest: /etc/nginx/nginx.conf

- name: Open firewall port 443
  firewalld:
    port: 443/tcp
    permanent: yes
    immediate: yes
    state: enabled

- name: Open firewall port 80
  firewalld:
    port: 80/tcp
    permanent: yes
    immediate: yes
    state: enabled

- name: I give up, disable selinux
  selinux:
    state: disabled
  register: task_result

- name: Reboot immediately if there was a change.
  shell: "sleep 5 && reboot"
  async: 1
  poll: 0
  when: task_result is changed

- name: Wait for the reboot to complete if there was a change.
  wait_for_connection:
    connect_timeout: 20
    sleep: 5
    delay: 5
    timeout: 300
  when: task_result is changed

- name: Start nginx service
  service:
    name: nginx
    state: started
    enabled: yes

roles/pmclain.NGINX/templates/etc/nginx/nginx.conf

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/nginx/conf.d/*.conf;

    server {
        listen       80 default_server;
        listen       [::]:80 default_server;
        server_name  _;
        root         /usr/share/nginx/html;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        location / {
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }

        return 301 https://$host$request_uri;
    }
}

Docker Role

The Docker task almost verbatim follows the installation instructions provided in Docker’s dev docs. The only additional items are the PIP packages needed by Ansible for interacting with the service.

roles/pmclain.NGINX/tasks/main.yml

- name: Remove existing Docker versions
  yum:
    name: ""
    state: absent
  vars:
    packages:
      - docker
      - docker-client
      - docker-client-latest
      - docker-common
      - docker-latest
      - docker-latest-logrotate
      - docker-logrotate
      - docker-engine

- name: Install yum-utils
  yum:
    name: yum-utils
    state: present

- name: Import Docker CE repository gpg key
  rpm_key:
    key: https://download.docker.com/linux/centos/gpg
    state: present

- name: Add Docker CE repository
  get_url:
    url: https://download.docker.com/linux/centos/docker-ce.repo
    dest: /etc/yum.repos.d/docker-ce.repo
    force: yes
    owner: root
    group: root
    mode: 0644

- name: Install python pip
  yum:
    name: python-pip
    state: present
    update_cache: yes

- name: Install Docker python library and requests
  pip:
    name:
      - docker
      - requests
    state: present

- name: Install Docker CE and supporting packages
  yum:
    name: ""
    state: present
    update_cache: yes
  vars:
    packages:
      - containerd.io
      - docker-ce
      - docker-ce-cli

- name: Start Docker service
  service:
    name: docker
    state: started
    enabled: yes

Now we’re ready to move on to getting Jenkins up and running. Site Build Part Two - Jenkins in Docker