GoCD Continuous Delivery 26.1.0
Overview
GoCD is the open source continuous delivery server from Thoughtworks. You model your build, test and release process as pipelines of stages, jobs and tasks. Changes in your source repositories (materials) or other pipelines trigger them. GoCD runs the jobs on agents and shows where every change is on the path to production (the value stream map).
This guide covers the GoCD Continuous Delivery 26.1.0 on Ubuntu 24.04 AMI from SecureAnyCloud on Amazon EC2. It is one version of the SecureAnyCloud Hardened Apache Subversion (SVN) & CI/CD Tools listing on AWS Marketplace. You pick it as the software version when you subscribe and launch.
What you get:
- No shared password and no shared keys. First boot generates a 24-character
adminpassword on your instance and writes it to/var/lib/sac/secrets/sac-credentials.txt(root only). GoCD's password file keeps only a bcrypt hash of it. First boot also creates GoCD's agent auto-register key, server ID, token-generation key and webhook secret. GoCD creates its cipher key (cipher.aes, which encrypts stored secrets) at its first start on your instance. GoCD refuses to start on this image until first boot has secured it - A credential for everything. Every page and every API call needs a signed-in user or the admin's credentials. Anonymous requests get the sign-in page or
401, and sign-in attempts are rate-limited per client address - A local agent, ready to build. The instance's own agent registers at first boot with this instance's auto-register key and is enabled. GoCD's automatic approval of local agents is off, and any other agent stays Pending until an administrator enables it
- GoCD on loopback behind nginx. GoCD listens on
127.0.0.1:8153only. nginx serves it on port 443 with a certificate generated on your instance, and port 80 only redirects. nginx does not expose GoCD's agent endpoints, so remote agents connect over an SSH tunnel (Step 8) - No proprietary service wrapper. GoCD's own packages start the server and agent through the Tanuki Java Service Wrapper, which is not open source. This image does not install those packages. It runs GoCD's unmodified go.jar and agent-bootstrapper.jar (checked against their SHA-256) directly on Ubuntu's OpenJDK 21, under SecureAnyCloud's own systemd units, with the JVM options GoCD's wrapper configuration would have used
- Tasks kept away from GoCD's secrets and the metadata service. Pipeline tasks run as
gocdagent, a separate account that cannot read or change GoCD's configuration, cipher key or password file. They run inside a systemd sandbox that denies the EC2 instance metadata service - Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on
What is on the image: GoCD 26.1.0 (build 22803; Apache-2.0, the server and agent jars from GoCD's signed Debian repository), OpenJDK 21, git and nginx from Ubuntu 24.04, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.
Default user: ubuntu. Architecture: x86_64. Recommended instance type: m5.large.
Read this before you share the instance: pipelines run commands on it. Pipeline tasks run on this instance as the gocdagent user. Anyone who can create or change a pipeline can run any command as that user, and those commands can reach your VPC and the internet. Only the instance metadata service is blocked.
Let only trusted people create or change pipelines. Restrict outbound security group rules as far as your pipelines allow, keep the instance metadata hop limit at 1, and attach no instance profile (or a least-privilege one). Step 7 explains each point.
Trademark notice: GoCD is a trademark of Thoughtworks, Inc. SecureAnyCloud is not affiliated with, endorsed by or sponsored by Thoughtworks. The name is used only to identify the open source software in this image, which is the unmodified upstream GoCD release.
Prerequisites
- An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet
- A security group that allows TCP 443 (GoCD over HTTPS) and TCP 80 (redirects to HTTPS) from the networks your users work from, and TCP 22 (SSH) from your own network only. GoCD needs no other inbound port
Launch the instance
In the AWS Marketplace listing, choose Continue to Subscribe, then Continue to Configuration. Pick GoCD Continuous Delivery 26.1.0 on Ubuntu 24.04 as the software version, choose your Region, and select Launch through EC2.
Choose m5.large, your key pair, a subnet and the security group described above.
GoCD keeps its pipeline history and artifacts under /var/lib/go-server. The local agent keeps its working copies under /var/lib/go-agent. Both are on the root volume, so raise its size under Configure storage if you keep many artifacts.
Under Advanced details, leave IAM instance profile empty (or choose a least-privilege one). Set Metadata version to V2 only and Metadata response hop limit to 1.
To launch with the AWS CLI instead, replace the values in angle brackets. This command sets a 50 GiB root volume and no instance profile:
aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
--instance-type m5.large --key-name <your-key-pair> \
--security-group-ids <your-security-group> --subnet-id <your-subnet> \
--metadata-options HttpTokens=required,HttpEndpoint=enabled,HttpPutResponseHopLimit=1 \
--block-device-mappings 'DeviceName=/dev/sda1,Ebs={VolumeSize=50,VolumeType=gp3}' \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=gocd}]'Connect to your instance
The SSH user for GoCD Continuous Delivery 26.1.0 on Ubuntu 24.04 is ubuntu. Connect with your key pair:
ssh -i <your-key.pem> ubuntu@<public-ip>Check the services
First boot works out the instance's name, generates the certificate and GoCD's secrets, and starts GoCD once to secure it. It then registers and enables the local agent, and proves over HTTPS that anonymous requests are refused and the new password is accepted. Only then does it open the web server. First boot takes about two minutes. Check the services:
systemctl is-active go-server go-agent nginx gocd-set-hostactive active active activegocd-set-host runs on every boot before nginx. It keeps the served name, the certificate and nginx's host rules in step (Step 9).
Only SSH (22) and nginx (80, 443) listen on the network. GoCD (8153) and the agent's own status port 8152 are on the loopback address only. Java shows the loopback address as the IPv4-mapped [::ffff:127.0.0.1].
The :53 entries are Ubuntu's local DNS resolver, :323 is the time service on the loopback address, and :68 is the DHCP client. List the listeners:
sudo ss -lntup | awk 'NR>1 {print $1, $5}' | sort -utcp 0.0.0.0:22 tcp 0.0.0.0:443 tcp 0.0.0.0:80 tcp 127.0.0.53%lo:53 tcp 127.0.0.54:53 tcp [::]:22 tcp [::]:443 tcp [::]:80 tcp [::ffff:127.0.0.1]:8152 tcp [::ffff:127.0.0.1]:8153 udp 127.0.0.1:323 udp 127.0.0.53%lo:53 udp 127.0.0.54:53 udp 172.31.94.75%ens5:68 udp [::1]:323The server runs as the go account and the agent as the separate gocdagent account. Both run on Ubuntu's OpenJDK 21 from SecureAnyCloud's units go-server.service and go-agent.service.
The agent shows as two Java processes: GoCD's agent bootstrapper, and the agent that the bootstrapper downloads from the server and starts. There is no service wrapper process:
ps -o user:10=,args= -C java | sed -E 's# -[^ ]+.* -jar # ... -jar #' pgrep -a wrapper || echo "no wrapper process" java -version 2>&1 | head -1gocdagent /usr/lib/jvm/java-21-openjdk-amd64/bin/java ... -jar /usr/share/go-agent/lib/agent-bootstrapper.jar -serverUrl http://127.0.0.1:8153/go gocdagent /usr/lib/jvm/java-21-openjdk-amd64/bin/java ... -jar agent.jar -serverUrl http://127.0.0.1:8153/go -sslVerificationMode FULL go /usr/lib/jvm/java-21-openjdk-amd64/bin/java ... -jar /usr/share/go-server/lib/go.jar no wrapper process openjdk version "21.0.12.1" 2026-08-18If nginx is not running, first boot has either not finished or failed. In both cases the web server stays closed. Check systemctl status gocd-firstboot and sudo tail -40 /var/log/sac/gocd-firstboot.log.
Get your address and password
Read the credentials file. Only root can read it, and it holds the address, the admin user name and password, and the certificate fingerprint:
sudo cat /var/lib/sac/secrets/sac-credentials.txtThe output looks like this:
# GoCD - generated on first boot. Unique to this instance; keep it safe.
# Sign in at GOCD_URL as GOCD_ADMIN_USER / GOCD_ADMIN_PASSWORD. Compare GOCD_TLS_SHA256 with the certificate your browser
# shows (or install your own certificate: see the guide). Rotate the password:
sudo gocd-set-password GOCD_URL=https://ec2-203-0-113-10.compute-1.amazonaws.com/go/ GOCD_ADMIN_USER=admin GOCD_ADMIN_PASSWORD=<GOCD_ADMIN_PASSWORD> GOCD_TLS_SHA256=EB:F0:B7:16:33:36:CA:31:51:F4:02:53:E6:15:B5:D4:5C:99:E2:41:5B:F2:41:9D:24:FD:3B:65:D7:01:07:7ETo print everything except the password, run:
sudo grep -E '^GOCD_(URL|ADMIN_USER|TLS_SHA256)=' /var/lib/sac/secrets/sac-credentials.txtGOCD_URL=https://ec2-203-0-113-10.compute-1.amazonaws.com/go/ GOCD_ADMIN_USER=admin GOCD_TLS_SHA256=EB:F0:B7:16:33:36:CA:31:51:F4:02:53:E6:15:B5:D4:5C:99:E2:41:5B:F2:41:9D:24:FD:3B:65:D7:01:07:7EYou can confirm the whole path from the instance itself. The verification helper goes through nginx over HTTPS with the certificate pinned. It checks four things:
- GoCD's pages and API refuse anonymous requests
- The agent endpoints and GoCD's anonymous health and version pages are not served
- Wrong passwords and well-known default passwords are refused
- This instance's password signs in on the login form and the API
Run the helper:
sudo /usr/local/sbin/gocd-verify.shGOCD_ROUNDTRIP_OK (HTTPS: 13 API routes + schedule + unsigned webhook 401 and 7 UI routes -> login anonymously, 11 agent endpoints 403, health/version 404, wrong/default passwords refused; the minted password accepted on the API and the login form, session admin)Change the password with gocd-set-password. With no argument, it generates a new 24-character password. With -, it reads yours from standard input (at least 16 printable characters, no spaces, quotes or backslashes).
The command never accepts the password as an argument, so the password does not appear in the process list or your shell history. GoCD picks up the change within a few seconds without a restart, so running pipelines are not interrupted. The command updates /var/lib/sac/secrets/sac-credentials.txt. It is also the way back in if you lose the password:
$
sudo gocd-set-password gocd-set-password: the admin password was replaced (sudo cat /var/lib/sac/secrets/sac-credentials.txt)To choose your own password, run:
$ read -rs NEWPW && printf '%s' "$NEWPW" | sudo gocd-set-password - ; unset NEWPW gocd-set-password: the admin password was replaced (sudo cat /var/lib/sac/secrets/sac-credentials.txt)The password file (/etc/gocd-auth/password.properties, readable by root and the go server account only) keeps a bcrypt hash at cost 12, never the password itself:
sudo sed -nE 's/^(admin:\$2y\$[0-9]+\$).*/\1.../p' /etc/gocd-auth/password.propertiesadmin:$2y$12$...Sign in
Open GOCD_URL in your browser. The certificate is self-signed until you install your own (Step 9), so the browser warns once. Compare the fingerprint it shows with GOCD_TLS_SHA256.
GoCD shows its sign-in page. Sign in with admin and the password from Step 4. There is no sign-up page.
The dashboard lists your pipelines, grouped, with the result of each stage of their latest run. Until you create the pipeline from Step 6: GoCD opens its pipeline wizard.
The Agents page lists the build agents. The instance's own agent, sac-local-agent, registered at first boot with this instance's auto-register key. It is enabled and idle.
Under Admin > Authorization Configuration, you can see how sign-in works: GoCD's bundled password file plugin reads /etc/gocd-auth/password.properties. The admin user is GoCD's only administrator.
Add users by adding one line per user to the password file in the form name:bcrypt-hash. For example, generate a hash with htpasswd -nbBC 12 <name> <password> on a trusted machine. Make users administrators, or give them only view or operate rights on pipeline groups, under Admin > Security > Role Configuration and the pipeline groups' permissions.
Anyone who can edit a pipeline can run commands on this instance (Step 7), so give edit rights only to trusted people. For single sign-on, install an authorization plugin such as GoCD's LDAP or GitHub OAuth plugin.
Create and run a pipeline
Choose Admin > Pipelines > Create a new pipeline, or choose Add new pipeline on the dashboard. Fill in the wizard with these details:
- A material: your git repository's URL and branch. Use Test Connection to check it
- A pipeline name
- A stage and a job with its task, for example a script such as
make test
Choose Save + Run This Pipeline to start it. GoCD then runs the pipeline whenever the material changes (it polls the repository every minute) and whenever you choose Trigger.
The job's Console tab shows the task's output. The task ran as the gocdagent user on this instance, in the local agent's working directory.
Do the same from a script with GoCD's REST API. This round trip runs on the instance itself. It creates a pipeline whose material is a small public repository on GitHub. The pipeline's one task prints who it ran as, lists the checkout, tries the instance metadata service and tries to read GoCD's configuration. The script runs the pipeline, waits for the result and prints the job's console.
The script reads the admin password from the credentials file into a file only. The password is never printed or put on a command line.
D=$(mktemp -d) && cd "$D" && umask 077
HOST=$(sudo sed -n 's#^GOCD_URL=https://\([^/]*\)/.*$#\1#p' /var/lib/sac/secrets/sac-credentials.txt) B="https://${HOST}/go" C=(-s --max-time 60 --cacert /etc/ssl/gocd/gocd.crt --resolve "${HOST}:443:127.0.0.1" -H 'Accept: application/vnd.go.cd+json')
# the password goes into a curl config file (printf is a shell builtin), never onto a command line printf 'user = "admin:%s"\n' "$(sudo sed -n 's/^GOCD_ADMIN_PASSWORD=//p' /var/lib/sac/secrets/sac-credentials.txt)" > auth.cfg A=("${C[@]}" -K auth.cfg)
# 1. a pipeline: one git material, one stage, one job, one task cat > pipeline.json <<'JSON' {"group": "guide", "pipeline": {"name": "hello-sac", "materials": [{"type": "git", "attributes": {"url": "https://github.com/octocat/Hello-World.git", "branch": "master"}}], "stages": [{"name": "build", "jobs": [{"name": "hello", "tasks": [{"type": "exec", "attributes": {"command": "/bin/sh", "arguments": ["-c", "echo \"Hello from GoCD as $(id -un) in $(pwd)\"; ls; echo \"metadata service from a task: $(curl -s -m 3 -o /dev/null -w '%{http_code}' -X PUT -H 'X-aws-ec2-metadata-token-ttl-seconds: 60' http://169.254.169.254/latest/api/token)\"; cat /etc/go/cruise-config.xml > /dev/null || true"]}}]}]}]}} JSON curl "${A[@]}" -o /dev/null -w 'create pipeline: %{http_code}\n' -X POST -H 'X-GoCD-Confirm: true' \
-H 'Content-Type: application/json' --data @pipeline.json "$B/api/admin/pipelines"
# 2. run it (GoCD needs a few seconds to read the material first) and wait for the result for i in $(seq 1 24); do
S=$(curl "${A[@]}" -o /dev/null -w '%{http_code}' -X POST -H 'X-GoCD-Confirm: true' "$B/api/pipelines/hello-sac/schedule") [ "$S" = 202 ] && break; sleep 5 done echo "schedule: $S" for i in $(seq 1 60); do read -r N R < <(curl "${A[@]}" "$B/api/pipelines/hello-sac/history" | python3 -c 'import json,sys; p=json.load(sys.stdin)["pipelines"]; print(p[0]["counter"], p[0]["stages"][0].get("result", "") if p else "")' 2>/dev/null) case "$R" in Passed|Failed|Cancelled) break ;; esac; sleep 3 done echo "result: $R"
# 3. the job's console log: the task's own output (&1 / &2) and the job's result curl "${A[@]}" "$B/files/hello-sac/$N/build/1/hello/cruise-output/console.log" | grep -E '^(&1|&2|j0)\|' | sed -E 's/^..\|[0-9:.]+ //' cd / && rm -rf "$D"create pipeline: 200 schedule: 202 result: Passed Hello from GoCD as gocdagent in /var/lib/go-agent/pipelines/hello-sac README metadata service from a task: 000 cat: /etc/go/cruise-config.xml: Permission denied [go] Current job status: passedThe result 000 means the task could not reach the metadata service at all, and the configuration file is refused to the agent's account. To remove the example pipeline again, run:
D=$(mktemp -d) && cd "$D" && umask 077
HOST=$(sudo sed -n 's#^GOCD_URL=https://\([^/]*\)/.*$#\1#p' /var/lib/sac/secrets/sac-credentials.txt) B="https://${HOST}/go" printf 'user = "admin:%s"\n' "$(sudo sed -n 's/^GOCD_ADMIN_PASSWORD=//p' /var/lib/sac/secrets/sac-credentials.txt)" > auth.cfg A=(-s --max-time 60 --cacert /etc/ssl/gocd/gocd.crt --resolve "${HOST}:443:127.0.0.1" -H 'Accept: application/vnd.go.cd+json' -K auth.cfg) curl "${A[@]}" -o /dev/null -w 'delete pipeline: %{http_code}\n' -X DELETE "$B/api/admin/pipelines/hello-sac" curl "${A[@]}" -o /dev/null -w 'delete pipeline group: %{http_code}\n' -X DELETE "$B/api/admin/pipeline_groups/guide" cd / && rm -rf "$D"delete pipeline: 200 delete pipeline group: 200From your workstation, use the same calls with https://<host>/go/api/.... GoCD's API takes the admin's user name and password over HTTP Basic, and it is rate-limited per client address like the sign-in form. Keep the password in a curl config file (-K), not on the command line.
Send the Accept: application/vnd.go.cd+json header. For POST and DELETE, also send X-GoCD-Confirm: true.
CI risk: who can run what
GoCD runs other people's code, so deciding who may define pipelines is the main security decision. On this image:
- Pipeline tasks run on the instance as the
gocdagentuser. The local agent runs every job insidego-agent.service, asgocdagent. Anyone who can create or change a pipeline can run any command as that user. The same applies to anyone who can change the repository a pipeline builds, if the pipeline runs its scripts - Tasks can reach your VPC and the internet. Builds need to fetch code, packages and artifacts, so only the instance metadata service is blocked for the agent. A task can connect to anything the instance's security group and routing allow. Restrict outbound security group rules as far as your pipelines allow, and do not put this instance in a subnet that can reach systems a pipeline author should not
- Tasks cannot read GoCD's secrets. The server runs as
go. Its configuration (/etc/go/cruise-config.xml), the cipher key that decrypts stored secrets (/etc/go/cipher.aes), the password file and the server's data are owned bygoor root and closed to other accounts. A task running asgocdagentcannot read or change them. Secrets that a pipeline uses (secure environment variables) are handed to its jobs by design, so a pipeline author can still read the secrets of the pipelines they can edit - What a task cannot do here. The agent's sandbox applies to every task: no
sudoorsu(no new privileges), no capabilities, a read-only system outside/var/lib/go-agentand/var/log/go-agent, a private/tmp, and no route to the instance metadata service (169.254.169.254 and its IPv6 address). Neithergocdagentnorgohas a login shell. Both are in no group but their own (notdocker), cannot usecronorat, and are refused by SSH, so a task cannot start a process outside the sandbox - Keep the hop limit at 1. The metadata block covers the GoCD services. A metadata hop limit of 1 (Step 1) also keeps any container you later run on this host from fetching IMDSv2 tokens
- Attach no instance profile, or a least-privilege one. Other software on the instance, and root, can still reach the metadata service, so an instance profile's permissions are only as safe as the instance. Give pipelines cloud credentials as GoCD secure variables (or through a remote agent's own role), scoped to what the jobs need
- Put untrusted work on separate agents. To build code from people you do not fully trust, disable the local agent (Agents, select it, Disable). Run those jobs on separate agent machines that hold nothing you would miss (Step 8)
Check how systemd applies the sandbox and the metadata block to the agent (and so to every task), the server and nginx:
systemctl show go-agent -p User -p NoNewPrivileges -p ProtectSystem -p ProtectHome -p PrivateTmp -p CapabilityBoundingSet -p UMask
systemctl show go-server go-agent nginx -p Id -p IPAddressDenyUMask=0027 CapabilityBoundingSet= User=gocdagent PrivateTmp=yes ProtectHome=yes ProtectSystem=strict NoNewPrivileges=yes IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128 Id=go-server.service IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128 Id=go-agent.service IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128 Id=nginx.serviceCheck the two accounts and the routes out of the sandbox that are closed:
id go; id gocdagent getent passwd go gocdagent | cut -d: -f1,7 cat /etc/cron.allow
sudo sshd -T | grep -iE '^(denyusers|passwordauthentication|permitrootlogin)'uid=997(go) gid=987(go) groups=987(go) uid=995(gocdagent) gid=986(gocdagent) groups=986(gocdagent) go:/usr/sbin/nologin gocdagent:/usr/sbin/nologin root permitrootlogin no passwordauthentication no denyusers go denyusers gocdagentCheck who may read GoCD's secrets. The agent's account is refused:
sudo stat -c '%U:%G %a %n' /etc/go /etc/go/cruise-config.xml /etc/go/cipher.aes /etc/gocd-auth /etc/gocd-auth/password.properties /var/lib/go-server
sudo -u gocdagent head -c 0 /etc/go/cruise-config.xml /etc/go/cipher.aes /etc/gocd-auth/password.properties || echo "refused to gocdagent"go:go 750 /etc/go go:go 640 /etc/go/cruise-config.xml go:go 640 /etc/go/cipher.aes root:go 750 /etc/gocd-auth root:go 640 /etc/gocd-auth/password.properties go:go 750 /var/lib/go-server head: cannot open '/etc/go/cruise-config.xml' for reading: Permission denied head: cannot open '/etc/go/cipher.aes' for reading: Permission denied head: cannot open '/etc/gocd-auth/password.properties' for reading: Permission denied refused to gocdagentAgents and plugins
The local agent. sac-local-agent is enabled at first boot because it presented this instance's auto-register key. First boot then removes the key from the agent's files.
GoCD's automatic approval of agents on the same machine is off. Any other agent that registers stays Pending until an administrator enables it on the Agents page. To run more jobs at once, add agents on other machines rather than more work on this one.
Remote agents connect over SSH. nginx does not serve GoCD's agent endpoints on port 443, and GoCD itself listens on loopback only. An agent on another machine therefore reaches the server through an SSH tunnel to this instance. On the agent machine, with GoCD's agent installed from gocd.org and an SSH key for this instance, open the tunnel:
ssh -N -L 8153:127.0.0.1:8153 -i <your-key.pem> ubuntu@<public-ip>Start the agent with http://localhost:8153/go as its server URL. For example, run java -jar agent-bootstrapper.jar -serverUrl http://localhost:8153/go, or set that URL in the packaged agent's configuration. When the agent shows as Pending, enable it on the Agents page.
Keep the tunnel running, for example with autossh or a systemd unit. Give each remote agent its own key pair, and use a machine you are willing to let pipeline authors control.
Plugins. GoCD's bundled plugins are installed with it. They include the password file and LDAP authorization plugins and the YAML and JSON configuration repository plugins. To add more, put the plugin's jar in /var/lib/go-server/plugins/external (owned by go) and restart GoCD (sudo systemctl restart go-server).
Each plugin runs inside GoCD with its full access. Install only plugins you trust and keep them updated.
Your own host name and certificate
On every boot, gocd-set-host works out the name to serve. It uses the name you set, or else the instance's public DNS name from the instance metadata service (IMDSv2). GoCD's site URL follows that name. Check the helper:
systemctl is-enabled gocd-set-host.service
sudo gocd-set-hostname --bootenabled gocd-set-hostname: unchanged (ec2-203-0-113-10.compute-1.amazonaws.com)Use your own DNS name. Point a DNS record at the instance. An Elastic IP keeps the address fixed. Then run sudo gocd-set-hostname gocd.example.com.
The name is kept in /etc/sac/gocd-hostname for every later boot, and the credentials file is updated. nginx serves that name, redirects the instance's other addresses to it, and refuses browser requests from other origins. Because the certificate in place is the one SecureAnyCloud generated, it is re-created for the new name.
Know which certificates are replaced. Only a certificate that SecureAnyCloud generated is ever re-created. Its SHA-256 fingerprint is recorded, root-owned, in /var/lib/sac/data/gocd-cert.sha256, and a certificate is treated as SecureAnyCloud's only while it matches that record:
sudo cat /var/lib/sac/data/gocd-cert.sha256 openssl x509 -in /etc/ssl/gocd/gocd.crt -noout -fingerprint -sha256EB:F0:B7:16:33:36:CA:31:51:F4:02:53:E6:15:B5:D4:5C:99:E2:41:5B:F2:41:9D:24:FD:3B:65:D7:01:07:7E sha256 Fingerprint=EB:F0:B7:16:33:36:CA:31:51:F4:02:53:E6:15:B5:D4:5C:99:E2:41:5B:F2:41:9D:24:FD:3B:65:D7:01:07:7EInstall your own certificate from your CA, or from Let's Encrypt with a DNS challenge, by putting the full chain and the key at the same paths. Anything there that does not match the record is yours. It is never replaced, either on boot or when the name changes:
sudo install -m 0644 -o root -g root fullchain.pem /etc/ssl/gocd/gocd.crt
sudo install -m 0600 -o root -g root privkey.pem /etc/ssl/gocd/gocd.key
sudo nginx -t && sudo systemctl reload nginxGo back to a generated certificate by removing both files and running the helper with the name to serve:
$
sudo rm /etc/ssl/gocd/gocd.crt /etc/ssl/gocd/gocd.key $
sudo gocd-set-hostname gocd.example.comTo return to the instance's own public name on every boot, run sudo rm /etc/sac/gocd-hostname && sudo gocd-set-hostname --boot.
Backups and updates
GoCD keeps its configuration in /etc/go, including cipher.aes, which decrypts every secure variable. It keeps its database, pipeline history and artifacts in /var/lib/go-server. The password file is /etc/gocd-auth/password.properties.
Back these up in any of three ways: take an EBS snapshot of the root volume, use GoCD's own Admin > Server Maintenance > Backup (it writes to /var/lib/go-server/artifacts/serverBackups), or stop GoCD and copy the folders. A backup holds the cipher key, so keep it as safe as the instance:
sudo systemctl stop go-agent go-server
sudo tar -C / -czf /root/gocd-backup.tar.gz etc/go etc/gocd-auth var/lib/go-server
sudo systemctl start go-server go-agentUnattended security upgrades update Ubuntu packages, including OpenJDK and nginx:
systemctl is-enabled unattended-upgrades apt-daily-upgrade.timerenabled enabledGoCD itself is not an apt package on this image, because GoCD's packages would install the proprietary service wrapper. Unattended upgrades therefore does not touch it.
To move to a newer GoCD release, follow these steps:
- Back up first, then stop both services
- Replace /usr/share/go-server/lib/go.jar and /usr/share/go-agent/lib/agent-bootstrapper.jar with the jars from the new release's packages. They must be owned by root with mode 0644. Check their checksums against GoCD's release page
- Start the services again. GoCD upgrades its database on start, and the agent downloads its matching agent jars from the server
SecureAnyCloud also publishes a new version of this listing for new GoCD releases.
Security notes
- Credential first. Anonymous users get the sign-in page or
401and nothing else, on every page and API route. nginx does not serve GoCD's anonymous health and version endpoints or its agent endpoints - Rate-limited sign-in. The login form is limited per client address (10 a minute), and so are requests that carry credentials to the API
- Exact listeners. Only 22, 80 and 443 listen on the network (Step 3). GoCD and the agent's status port are on loopback, and port 80 only redirects to HTTPS. Open the ports in the security group only to the networks that need them
- Forwarded headers. nginx sends GoCD the configured host name,
https, port 443 and the real client address.X-Forwarded-Foris set from the connection and never passed through. nginx drops client-suppliedForwardedheaders and refuses state-changing requests and WebSocket upgrades from other origins. Responses carry HSTS - Instance metadata. systemd denies the EC2 instance metadata service to GoCD's server and agent (and so every pipeline task), nginx, first boot and the helper commands (Step 7). Launch with IMDSv2 required and a hop limit of 1, as in Step 1
- Secrets per instance. The admin password, GoCD's cipher key, agent auto-register key, server ID, token-generation key and webhook secret, the local agent's identity, the HTTPS certificate and the SSH host keys are all created on your instance's first boot. None is in the image
Build provenance and licence
GoCD 26.1.0 (build 22803) comes from GoCD's Debian repository at download.gocd.org. The repository's signing key is pinned to fingerprint 322259C82D3082B3E32AEC2ED8843F288816C449, and the packages' SHA-256 is checked against the signed index.
The build does not install the packages. It unpacks them without running their scripts, copies out only go.jar, agent-bootstrapper.jar and GoCD's licence byte for byte, and then removes the repository again.
GoCD's packages also carry the Tanuki Java Service Wrapper, which is proprietary. The build checks that no part of it is on the image. Verify the provenance yourself:
head -4 /usr/share/doc/sac/gocd-BUILD-INFO.txt sha256sum /usr/share/go-server/lib/go.jar /usr/share/go-agent/lib/agent-bootstrapper.jar dpkg-query -W go-server go-agent 2>&1
sudo find / -xdev \( -name 'wrapper*.jar' -o -name 'libwrapper*' -o -name 'wrapper.conf' -o -name 'wrapper-license*' \) 2>/dev/null | wc -lGoCD Continuous Delivery 26.1.0-22803 on Ubuntu 24.04 (sac AWS bundle member dev-cicd) go-server_26.1.0-22803_all.deb sha256 70bd6ba0b6fbfdbd8be8d5ffce2d8941ac285e7ae757eac27af7b85bc080e868 and go-agent_26.1.0-22803_all.deb sha256 c2d122ed855e019d07d5a89590f128d98e957f05136ff6985e6de8b735265063, downloaded at build time from https://download.gocd.org (Signed-By exactly key 322259C82D3082B3E32AEC2ED8843F288816C449); NOT installed: unpacked with dpkg-deb -x shipped from them, unmodified: /usr/share/go-server/lib/go.jar sha256 315080a2e8b342acd3fc741ed5bd0da7e28b4ad504bfc9d3f202fe5890266a31; /usr/share/go-agent/lib/agent-bootstrapper.jar sha256 69424acb64cd93e5a02cbf5bd3207833707b4607ff1fd4aecdce1042f6d82511; Apache-2.0 LICENSE NOT shipped: the debs' Tanuki Java Service Wrapper (wrapper binary, libwrapper, wrapper.jar, wrapper.conf) and their maintainer scripts; the server and agent run under sac's own systemd units 315080a2e8b342acd3fc741ed5bd0da7e28b4ad504bfc9d3f202fe5890266a31 /usr/share/go-server/lib/go.jar 69424acb64cd93e5a02cbf5bd3207833707b4607ff1fd4aecdce1042f6d82511 /usr/share/go-agent/lib/agent-bootstrapper.jar dpkg-query: no packages found matching go-server dpkg-query: no packages found matching go-agent 0GoCD is licensed under the Apache License 2.0. The libraries inside its jars carry their own open source licences. GoCD's source is at github.com/gocd/gocd (tag 26.1.0).
GoCD's security advisories are published on GitHub. SecureAnyCloud publishes a new version of this listing when a release fixes one.
Troubleshooting
- The sign-in page refuses the password. Read the password again from
/var/lib/sac/secrets/sac-credentials.txt(the user name isadmin). After you rungocd-set-password, the old password is refused 429 Too Many Requests. One address made too many sign-in attempts or API calls with credentials (the limit is 10 sign-ins a minute). Wait a minute403on a form or WebSocket from another site, or a connection dropped with no answer. The request named a host or origin that the instance does not serve. Use the name inGOCD_URL, or set your own withgocd-set-hostname(Step 9)- A pipeline stays scheduled and never runs. No enabled agent is idle. The local agent is disabled or busy, or a remote agent is still Pending (Step 8). Check the Agents page
- A material fails to update. GoCD (as
go) and the agent (asgocdagent) both fetch the repository. Check the URL with Test Connection. For a private repository, add its credentials to the material, which stores them encrypted with the instance's cipher key. Make sure the security group allows the outbound connection - A task cannot reach the metadata service or use
sudo. This is the sandbox working as designed (Step 7). Give pipelines cloud credentials as secure variables, or run them on a remote agent with its own role - Logs:
journalctl -u go-serverandjournalctl -u go-agent,/var/log/go-server/and/var/log/go-agent/,/var/log/nginx/access.logand/var/log/nginx/error.log, and/var/log/sac/gocd-firstboot.log
Support
SecureAnyCloud images come with 24/7 support. Email sales@secureanycloud.com with your AWS account ID, the instance ID and the output of sudo /usr/local/sbin/gocd-verify.sh.
For questions about GoCD itself, see the GoCD documentation.
Deploy on AWS
Launch GoCD Continuous Delivery 26.1.0 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com