Programster's Blog

Tutorials focusing on Linux, programming, and open-source

Deploy Semaphore With Docker

Simple Deployment

This version uses a local Bolt database within the container.

services:
  semaphore:
    restart: unless-stopped
    ports:
      - 80:3000
    image: semaphoreui/semaphore:latest
    volumes:
      - semaphore-config:/etc/semaphore # config.json location
      - semaphore-database:/var/lib/semaphore # database.boltdb location (Not required if using mysql or postgres)
    environment:
      - SEMAPHORE_DB_DIALECT=bolt
      - SEMAPHORE_ADMIN_PASSWORD
      - SEMAPHORE_ADMIN_NAME
      - SEMAPHORE_ADMIN_EMAIL
      - SEMAPHORE_ADMIN
      - TZ=Europe/London

volumes:
  semaphore-config:
    driver: local
  semaphore-database:
    driver: local

Env File

COMPOSE_PROJECT_NAME="semaphore"

# Set the login details of the initial admin user.
SEMAPHORE_ADMIN_PASSWORD="someStrongRandomPassword"
SEMAPHORE_ADMIN_NAME="My Full Name"
SEMAPHORE_ADMIN_EMAIL="myemail@gmail.com"
SEMAPHORE_ADMIN="myUsername"

Advanced Deployment

Unlike the previous (simple) deployment, this one uses a seperate PostgreSQL container to store the backend state, instead of an internetl Bolt database.

services:
  postgres:
    restart: unless-stopped
    image: postgres:14
    hostname: postgres
    volumes: 
      - semaphore-postgres:/var/lib/postgresql/data
    environment:
      - POSTGRES_USER=${DB_USER}
      - POSTGRES_PASSWORD=${DB_PASSWORD}
      - POSTGRES_DB=${DB_NAME}

  semaphore:
    restart: unless-stopped
    ports:
      - 80:3000
    image: semaphoreui/semaphore:latest
    environment:
      - SEMAPHORE_DB_USER=${DB_USER}
      - SEMAPHORE_DB_PASS=${DB_PASSWORD}
      - SEMAPHORE_DB_HOST=postgres
      - SEMAPHORE_DB_PORT=5432
      - SEMAPHORE_DB_DIALECT=postgres
      - SEMAPHORE_DB=${DB_NAME}
      - SEMAPHORE_PLAYBOOK_PATH=/tmp/semaphore/
      - SEMAPHORE_ADMIN_PASSWORD
      - SEMAPHORE_ADMIN_NAME
      - SEMAPHORE_ADMIN_EMAIL
      - SEMAPHORE_ADMIN
      - SEMAPHORE_ACCESS_KEY_ENCRYPTION
      - SEMAPHORE_LDAP_ACTIVATED='no'
    depends_on:
      - postgres 

volumes:
  semaphore-postgres:
    driver: local

Now create a .env file with our settings:

COMPOSE_PROJECT_NAME="semaphore"

# Set the database details. The most important thing is setting a random password.
DB_USER=semaphore
DB_PASSWORD="someRandomPasswordForTheDb"
DB_NAME="semaphore"

# Set the login details of the initial admin user.
SEMAPHORE_ADMIN_PASSWORD="someRandomPassword"
SEMAPHORE_ADMIN_NAME="My Name"
SEMAPHORE_ADMIN_EMAIL="myemail@gmail.com"
SEMAPHORE_ADMIN="myUsername"

# key for encrypting access keys in database. It must be generated by using the following command:
# head -c32 /dev/urandom | base64
SEMAPHORE_ACCESS_KEY_ENCRYPTION=

Be sure to change the password values, and read the comment for filling in the value for the SEMAPHORE_ACCESS_KEY_ENCRYPTION. You don't need to wrap it with quotes.

TLS Certificates

One needs to use a reverse proxy in order to get a TLS connection in place. You can either do that by setting up and using Nginx Proxy manager, or if you want to do it raw with Nginx, then the docs provides the following example site configuration:

server {
  listen 443 ssl;
  server_name  _;

  # add Strict-Transport-Security to prevent man in the middle attacks
  add_header Strict-Transport-Security "max-age=31536000" always;

  # SSL
  ssl_certificate /etc/nginx/cert/cert.pem;
  ssl_certificate_key /etc/nginx/cert/privkey.pem;

  # Recommendations from 
  # https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
  ssl_protocols TLSv1.1 TLSv1.2;
  ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
  ssl_prefer_server_ciphers on;
  ssl_session_cache shared:SSL:10m;

  # required to avoid HTTP 411: see Issue #1486 
  # (https://github.com/docker/docker/issues/1486)
  chunked_transfer_encoding on;

  location / {
    proxy_pass http://127.0.0.1/;
    proxy_set_header Host $http_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_buffering off;
    proxy_request_buffering off;
  }

  location /api/ws {
    proxy_pass http://127.0.0.1/api/ws;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Origin "";
  }
}

The most important thing to be aware of is that the /api/ws path requires websocket support.

Conclusion

Thats it, in future tutorials we will demonstrate how to create and run a job, along with how to pass in variables at the last possible second, and make use of the vault.

Last updated: 3rd April 2024
First published: 3rd April 2024