Single Linux Machine
This page explains how to install the Semaphore Enterprise Edition control plane on a single Linux Server.
Overview
If this is your first time using Semaphore, we suggest trying out Semaphore Cloud to see if the platform fits your needs. You can create a free trial account without a credit card and use every feature.
The self-hosted installation is recommended for users and teams that are already familiar with Semaphore.
Prerequisites
- A license
- A domain
- A Linux machine running Ubuntu. Preferably Ubuntu 24.04 LTS
- Minimum memory: 16GB RAM
- Minimum compute: 8 CPUs
- A public IP address.
- External access requires ports SSH (22), HTTP (80), and HTTPS (443) to be open
- SSH access to the machine
- Sudo or root access in the machine
Ensure that your VMs are running with hardware-supported virtualization mode enabled. Without this feature, Semaphore might not run at all, even when the minimum hardware requirements are met.
Step 1 - Obtain a License
The Enterprise Edition of Semaphore requires a license to operate. Depending on your company size, you may qualify for a free license.
To obtain a license and learn more, see the how to obtain a license page.
Step 2 - Define the domain
We highly recommend installing Semaphore on a subdomain. Installing Semaphore on your base domain might interfere with other services running on the same domain.
If your base domain is example.com, you should define a subdomain such as ci.example.com for your Semaphore installation.
Step 3 - Prepare the machine
- Ubuntu
- Google Cloud Compute
- AWS EC2 Virtual Machine
-
Copy the license file to the VM
Copy license file to VMscp path/to/license-file <user>@<hostname>:/tmp -
Open a terminal on your Linux machine, e.g., using SSH
Connect to your machinessh <user>@<hostname> -
Create a user to run Semaphore and give them sudo powers
Remote shell: create semaphore user with sudosudo adduser semaphore
sudo usermod -aG sudo semaphore
su - semaphore -
Create a folder to store the config files
Remote shell: Create install foldermkdir semaphore-install
cd semaphore-install -
Create a file with the following environment variables
DOMAIN: subdomain + domain for your installationLICENSE_FILE: license file nameIP_ADDRESS: public IP address of the machineROOT_EMAIL: email for the owner/administrator of the Semaphore serverROOT_NAME: Name for the owner/administrator of the Semaphore server
Remote shell: create semaphore-config file (example)echo export DOMAIN="ci.example.com" > semaphore-config
echo export LICENSE_FILE="license-file-name.txt" >> semaphore-config
echo export IP_ADDRESS="1.2.3.4" >> semaphore-config
echo export ROOT_EMAIL="admin@example.com" >> semaphore-config
echo export ROOT_NAME=\"Semaphore admin\" >> semaphore-config -
Move the license file to the installation directory
Remote shell: move license filesource semaphore-config
sudo mv /tmp/${LICENSE_FILE} .
sudo chown semaphore:semaphore $LICENSE_FILE -
Install certbot
remote shell: install certbotsudo apt-get update
sudo apt-get -y install certbot
-
Install Google Cloud SDK
-
Log in to your Google Cloud
Login to GCPgcloud auth login -
Create a Google Cloud Project for your Semaphore server. Take note of the Google Project ID
-
Initialize a config file for your Semaphore and GCP project
DOMAIN: subdomain + domain for your installationLICENSE_FILE: license file nameROOT_EMAIL: email for the owner/administrator of the Semaphore serverROOT_NAME: Name for the owner/administrator of the Semaphore server
Remote shell: create semaphore-config file (example)echo export DOMAIN="your-subdomain-and-domain" > semaphore-config
echo export LICENSE_FILE="license-file-name.txt" >> semaphore-config
echo export ROOT_EMAIL="administrator-email" >> semaphore-config
echo export ROOT_NAME=\"administrator-name\" >> semaphore-config
echo export GOOGLE_CLOUD_PROJECT_ID="your-project-id" >> semaphore-config
echo export GOOGLE_INSTANCE_NAME="name-for-your-VM" >> semaphore-config
echo export GOOGLE_CLOUD_ZONE="gcp-region" >> semaphore-config
echo export SSH_KEY_PUBLIC="path-to-your-public-ssh-key" >> semaphore-config
echo export SSH_KEY_PRIVATE="path-to-your-private-ssh-key" >> semaphore-configShow me an example config
Create a config file for Google projectecho export DOMAIN="ci.example.com" > semaphore-config
echo export LICENSE_FILE="license-851b7f23-7408-4e75-8591-25b90d1c7dac.txt" >> semaphore-config
echo export ROOT_EMAIL="admin@example.com" >> semaphore-config
echo export ROOT_NAME=\"Semaphore Admin\" >> semaphore-config
echo export GOOGLE_CLOUD_PROJECT_ID="my-semaphore-323342" >> semaphore-config
echo export GOOGLE_INSTANCE_NAME="semaphore-control-machine" >> semaphore-config
echo export GOOGLE_CLOUD_ZONE="us-central1-a" >> semaphore-config
echo export SSH_KEY_PUBLIC="$HOME/.ssh/id_rsa.pub" >> semaphore-config
echo export SSH_KEY_PRIVATE="$HOME/.ssh/id_rsa" >> semaphore-config -
Switch to the Semaphore project on GCP
Switch to your Semaphore projectsource semaphore-config
gcloud config set project "${GOOGLE_CLOUD_PROJECT_ID}" -
Create a Virtual Machine. The following command creates the minimum recommended VM to run Semaphore
Create a VMgcloud compute instances create ${GOOGLE_INSTANCE_NAME} \
--zone=${GOOGLE_CLOUD_ZONE} \
--project=${GOOGLE_CLOUD_PROJECT_ID} \
--network-interface=network-tier=PREMIUM,stack-type=IPV4_ONLY,subnet=default \
--scopes=https://www.googleapis.com/auth/devstorage.read_only,https://www.googleapis.com/auth/logging.write,https://www.googleapis.com/auth/monitoring.write,https://www.googleapis.com/auth/service.management.readonly,https://www.googleapis.com/auth/servicecontrol,https://www.googleapis.com/auth/trace.append \
--tags=http-server,https-server \
--machine-type=e2-standard-8 \
--image-family=ubuntu-2404-lts-amd64 \
--image-project=ubuntu-os-cloud \
--boot-disk-size=64GB \
--metadata "ssh-keys=$(whoami):$(cat $SSH_KEY_PUBLIC)" -
Get the public IP of your new server
Get public IP of VMexport IP_ADDRESS=$(gcloud compute instances describe ${GOOGLE_INSTANCE_NAME} --zone ${GOOGLE_CLOUD_ZONE} --format='text(networkInterfaces.[].accessConfigs.[].natIP)' | awk -F': ' '{print $2}') -
Add the IP address to your Google config file
echo export IP_ADDRESS=${IP_ADDRESS} >> semaphore-config -
Open ports HTTP (80) and HTTPS (443)
Open firewall portsgcloud compute firewall-rules create allow-http \
--allow=tcp:80 \
--direction=INGRESS \
--source-ranges=0.0.0.0/0 \
--target-tags=semaphore-allow-access
gcloud compute firewall-rules create allow-https \
--allow=tcp:443 \
--direction=INGRESS \
--source-ranges=0.0.0.0/0 \
--target-tags=semaphore-allow-access
gcloud compute instances add-tags ${GOOGLE_INSTANCE_NAME} --zone ${GOOGLE_CLOUD_ZONE} --tags semaphore-allow-access -
Copy the config and license files to your new VM
Copy semaphore-config to VMgcloud compute scp --zone=${GOOGLE_CLOUD_ZONE} --project=${GOOGLE_CLOUD_PROJECT_ID} --ssh-key-file "${SSH_KEY_PRIVATE}" semaphore-config ${GOOGLE_INSTANCE_NAME}:/tmp/
gcloud compute scp --zone=${GOOGLE_CLOUD_ZONE} --project=${GOOGLE_CLOUD_PROJECT_ID} --ssh-key-file "${SSH_KEY_PRIVATE}" $LICENSE_FILE ${GOOGLE_INSTANCE_NAME}:/tmp/ -
SSH into your VM
SSH into the VMgcloud compute ssh --zone=${GOOGLE_CLOUD_ZONE} --project=${GOOGLE_CLOUD_PROJECT_ID} --ssh-key-file ~/.ssh/id_ed25519 ${GOOGLE_INSTANCE_NAME} -
Create a semaphore user with sudo powers
Remote shell: create semaphore user with sudosudo adduser semaphore
sudo usermod -aG sudo semaphore
su - semaphore -
Create an install directory and move the config and license files
Remote shell: Create install foldermkdir semaphore-install
cd semaphore-install
sudo mv /tmp/semaphore-config .
sudo chown semaphore:semaphore semaphore-config
source semaphore-config
sudo mv /tmp/${LICENSE_FILE} .
sudo chown semaphore:semaphore $LICENSE_FILE
-
Install the AWS CLI
-
Login to your AWS Account
Login to AWSaws configure -
Create a config file to store Semaphore and AWS settings
DOMAIN: subdomain + domain for your installationLICENSE_FILE: license file nameROOT_EMAIL: email for the owner/administrator of the Semaphore serverROOT_NAME: Name for the owner/administrator of the Semaphore serverAWS_SSH_KEY: a name for the SSH key file to connect to the EC2 VMAWS_SECURITY_GROUP: name for the AWS security group for the EC2 VM
Create config file for Semaphore and AWSecho export DOMAIN="your-subdomain-and-domain" > semaphore-config
echo export LICENSE_FILE="license-file-name.txt" >> semaphore-config
echo export ROOT_EMAIL="administrator-email" >> semaphore-config
echo export ROOT_NAME=\"Administrator Name\" >> semaphore-config
echo export AWS_SSH_KEY="your-ssh-key-name" >> semaphore-config
echo export AWS_SECURITY_GROUP="your-security-group-name" >> semaphore-configShow me an example config
Create a config file for Google projectecho export DOMAIN="ci.example.com" > semaphore-config
echo export LICENSE_FILE="license-851b7f23-7408-4e75-8591-25b90d1c7dac.txt" >> semaphore-config
echo export ROOT_EMAIL="admin@example.com" >> semaphore-config
echo export ROOT_NAME=\"Semaphore admin\" >> semaphore-config
echo export AWS_SSH_KEY="ssh-keys-semaphore" >> semaphore-config
echo export AWS_SECURITY_GROUP="security-group-semaphore" >> semaphore-config -
Create a security group to control access to your EC2 machine
Create security groupsource semaphore-config
export SECURITY_GROUP_ID=$(aws ec2 create-security-group \
--group-name ${AWS_SECURITY_GROUP} \
--description "Security group for Semaphore instance" \
--output text \
--query 'GroupId')
echo export SECURITY_GROUP_ID=${SECURITY_GROUP_ID} >> semaphore-config -
Open ports SSH (22), HTTP (80), and HTTPS (443)
Open firewall portsaws ec2 authorize-security-group-ingress --group-id $SECURITY_GROUP_ID --protocol tcp --port 22 --cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress --group-id $SECURITY_GROUP_ID --protocol tcp --port 80 --cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress --group-id $SECURITY_GROUP_ID --protocol tcp --port 443 --cidr 0.0.0.0/0 -
Create SSH keys to access the VM
Create SSH keypairaws ec2 create-key-pair --key-name $AWS_SSH_KEY --query 'KeyMaterial' --output text > ${AWS_SSH_KEY}.pem
chmod 600 ${AWS_SSH_KEY}.pem -
Assign a subnet for the VM. Check the available subnets
Find availale subnetsaws ec2 describe-subnets -
Create a subnet for the VM, adjust the values as needed
Create a subnet for the VMaws ec2 create-subnet \
--vpc-id "my-SubnetId" \
--cidr-block "my-CidrBlock" \
--availability-zone "my-AvailabilityZoneId" -
Add the
SUBNET_IDto the config fileAdd subnet id to the config fileecho export SUBNET_ID="my-new-SubnetId" >> semaphore-config -
Create the EC2 VM. This command creates the minimum recommended instance to run Semaphore
Create EC2 VMexport INSTANCE_ID=$(aws ec2 run-instances \
--image-id resolve:ssm:/aws/service/canonical/ubuntu/server/24.04/stable/current/amd64/hvm/ebs-gp3/ami-id \
--instance-type t2.2xlarge \
--security-group-ids $SECURITY_GROUP_ID \
--key-name $AWS_SSH_KEY \
--subnet-id $SUBNET_ID \
--count 1 \
--associate-public-ip-address \
--output text \
--query 'Instances[0].InstanceId') -
Add the
INSTANCE_IDto the config fileAdd INSTANCE_ID to the config fileecho export INSTANCE_ID=$INSTANCE_ID >> semaphore-config -
Retrieve the IP address assigned to the new VM
Get IP Address of new VMexport IP_ADDRESS=$(aws ec2 describe-instances --instance-ids $INSTANCE_ID \
--query 'Reservations[0].Instances[0].PublicIpAddress' \
--output text)
echo export IP_ADDRESS=${IP_ADDRESS} >> semaphore-config -
Copy the config file to the VM
Copy config file with scpscp -i ${AWS_SSH_KEY}.pem semaphore-config ubuntu@{IP_ADDRESS}:/tmp -
SSH into the machine
Connect with your EC2 instancessh -i ${AWS_SSH_KEY}.pem ubuntu@${IP_ADDRESS} -
Create semaphore user in the VM
Remote shell: create semaphore user with sudosudo adduser semaphore
sudo usermod -aG sudo semaphore
su - semaphore -
Create an install directory and move the config and license files
Remote shell: Create install foldermkdir semaphore-install
cd semaphore-install
sudo mv /tmp/semaphore-config .
sudo chown semaphore:semaphore semaphore-config
source semaphore-config
sudo mv /tmp/${LICENSE_FILE} .
sudo chown semaphore:semaphore $LICENSE_FILE
Step 4 - Create DNS A Records
Configure your DNS by creating two A records that point to the server IP address.
-
Go to your domain provider's DNS settings
-
Create A record for your subdomain
- Type: A
- Name: your subdomain + domain (e.g.
ci.example.com) - Value: the public IP address of your Linux machine
-
Create a wildcard record
- Type: A
- Name: your subdomain + domain (e.g.
*.ci.example.com) - Value: the public IP address of your Linux machine
-
Wait for DNS propagation (typically a few minutes)
You can verify the creation of the A record in the Online Dig Tool for:
ci.example.comid.ci.example.com
Step 5 - Create TLS certificates
You must create a wildcard TLS certificate for your Semaphore subdomain, e.g., ci.example.com. Note that this certificate expires three months after generation.
Certificates are not auto-renewed. Once expired, you must execute this step again to generate new certificates and then re-run Helm upgrade with the same argument.
-
Still logged in your VM, install certbot in the Linux server
Remote shell: install certbotsudo apt-get update
sudo apt-get -y install certbot -
Run certbot to create a TLS certificate
Remote shell: create certificates with certbotsource semaphore-config
mkdir -p certs
certbot certonly --manual --preferred-challenges=dns \
-d "*.${DOMAIN}" \
--register-unsafely-without-email \
--work-dir certs \
--config-dir certs \
--logs-dir certs -
You are prompted to create a DNS TXT record to verify ownership of the domain
Remote shell: certbot challenge messagePlease deploy a DNS TXT record under the name:
_acme-challenge.ci.example.com.
with the following value:
EL545Zty7vUUvIHQRSkwxXTWsirldw91enasgB5uOHs -
Go to your domain's console and create the DNS TXT record required by certbot. Wait for the record to be propagated
tipYou can verify the creation of the TXT record in the Google Dig Tool. Type the challenge DNS TXT record and check if its value corresponds to the correct value.
-
Continue the certbot process. You should see a message like this
Remote shell: certificate generated messageSuccessfully received the certificate.
Certificate is saved at: certs/live/ci.example.com/fullchain.pem
Key is saved at: certs/live/ci.example.com/privkey.pem
This certificate expires on 2025-02-27.
These files will be updated when the certificate renews. -
Check the existence of the certificate files on the following paths. You will require both files during the Semaphore installation.
- Full chain certificate:
./certs/live/$DOMAIN/fullchain.pem - Private key certificate:
./certs/live/$DOMAIN/privkey.pem
- Full chain certificate:
-
You may delete the DNS TXT record from your domain at this point. It's no longer needed.
Step 6 - Install K3s and CRDs
In this step, we install and configure K3s to run Semaphore.
-
Still inside the remote shell in your Linux machine, install Helm with:
Remote shell: install Helmcurl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
chmod 700 get_helm.sh
sudo ./get_helm.sh -
Install K3s
Remote shell: install k3scurl -sfL https://get.k3s.io | sudo K3S_KUBECONFIG_MODE="644" sh - -
Add
KUBECONFIGto your shell environmentRemote shell: set KUBECONFIG in your bashrcecho export KUBECONFIG=/etc/rancher/k3s/k3s.yaml >> ~/.bashrc
source ~/.bashrc -
Optionally, install k9s to manage and observe your K3s system
wget https://github.com/derailed/k9s/releases/latest/download/k9s_linux_amd64.deb && sudo apt install ./k9s_linux_amd64.deb && rm k9s_linux_amd64.deb -
Install Emissary Ingress Controller
Remote shell: install Emissary CRDkubectl apply -f https://app.getambassador.io/yaml/emissary/3.9.1/emissary-crds.yaml
kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system
Step 7 - Install Semaphore
This step installs the Enterprise Edition. If you want to install the Community Edition, see the Community Installation page.
-
Sanity check your environment before installing Semaphore. These commands should return valid values
Remote shell: check if ready to installsource semaphore-config
echo "DOMAIN=${DOMAIN}"
echo "IP_ADDRESS=${IP_ADDRESS}"
ls certs/live/${DOMAIN}/fullchain.pem certs/live/${DOMAIN}/privkey.pem $LICENSE_FILE -
Install Semaphore with Helm. The installation usually takes between 10 and 30 minutes
Remote shell: install Semaphorehelm upgrade --install semaphore "oci://ghcr.io/semaphoreio/semaphore" \
--debug \
--version v1.5.0 \
--timeout 30m \
--set global.edition=ee \
--set global.license="$(cat ${LICENSE_FILE})"\
--set global.rootUser.email="${ROOT_EMAIL}" \
--set global.rootUser.name="${ROOT_NAME}" \
--set global.domain.ip="${IP_ADDRESS}" \
--set global.domain.name="${DOMAIN}" \
--set ingress.enabled=true \
--set ingress.ssl.enabled=true \
--set ingress.className=traefik \
--set ingress.ssl.type=custom \
--set ingress.ssl.crt="$(cat certs/live/${DOMAIN}/fullchain.pem | base64 -w 0)" \
--set ingress.ssl.key="$(cat certs/live/${DOMAIN}/privkey.pem | base64 -w 0)" -
Once installed, you should see this message
Remote shell: installation complete message=============================================================================================
Congratulations, Semaphore has been installed successfully!
To start using the app, go to: https://id.ci.tomfern.com/login
You can fetch credentials for the login running this command:
echo "Email: $(kubectl get secret semaphore-authentication -n default -o jsonpath='{.data.ROOT_USER_EMAIL}' | base64 -d)"; echo "Password: $(kubectl get secret semaphore-authentication -n default -o jsonpath='{.data.ROOT_USER_PASSWORD}' | base64 -d)"; echo "API Token: $(kubectl get secret semaphore-authentication -n default -o jsonpath='{.data.ROOT_USER_TOKEN}' | base64 -d)"
============================================================================================= -
Execute the shown command to retrieve the login credentials.
Remote shell: get login credentialsecho "Email: $(kubectl get secret semaphore-authentication -n default -o jsonpath='{.data.ROOT_USER_EMAIL}' | base64 -d)"; echo "Password: $(kubectl get secret semaphore-authentication -n default -o jsonpath='{.data.ROOT_USER_PASSWORD}' | base64 -d)"; echo "API Token: $(kubectl get secret semaphore-authentication -n default -o jsonpath='{.data.ROOT_USER_TOKEN}' | base64 -d)"
Email: root@example.com
Password: AhGg_2v6uHuy7hqvNmeLw0O4RqI=
API Token: nQjnaPKQvW6TqXtpTNSx -
Backup your
semaphore-configfile in a safe place. It is required to upgrade Semaphore, update your license, and renew expired certificates -
On your browser, open the subdomain where Semaphore was installed prefixed with
id, e.g.,id.ci.example.com -
Fill in the username and password. You might be prompted to set a new password

-
Open the server menu and select Settings

-
Select Initialization jobs

-
Select the Environment Type to
Self-hosted Machine -
Select Machine Type to
s1-kubernetesand press Save changesnoteYou should press Save changes even if the options were already selected.
-
Return to the Semaphore initial page. On the Learn tab, you'll find the onboarding guide. Follow it to complete the setup and build your first project
