The hardened Octopus Server Linux Container is a minimal variant of the Octopus Server Linux Container. It’s built for teams that need a reduced attack surface to meet security or compliance requirements.
Compared to the standard image, the hardened image:
- Is built on a Docker Hardened Image minimal base, with no shell, package manager, or general-purpose OS tools.
- Runs as a non-root user (UID
1001) by default. - Supports running as an arbitrary UID, as long as that UID is a member of group
0. This makes it compatible with OpenShift’srestrictedsecurity context constraints. - Contains no files with the SUID or SGID bit set, and no world-writable paths outside
/tmpand/var. - Doesn’t run Docker-in-Docker and doesn’t need privileged permissions.
The hardened image is opt-in. It’s published alongside the standard image, and the standard image is unchanged.
Security updates and compatibility
The hardened image exists to keep up with security changes. When the base image or the native libraries it ships with are updated, those changes flow into the next Octopus Server release without any compatibility shims.
The most visible example is OpenSSL. The hardened image uses OpenSSL for TLS, and OpenSSL updates regularly deprecate older TLS versions, cipher suites, and key sizes. When that happens, clients and targets that only support the older options may stop being able to connect to Octopus Server. This can include:
- Older Tentacles, or Tentacles running on older operating systems with outdated TLS libraries.
- SSH targets that only offer deprecated key exchange algorithms or ciphers.
- Older API clients, CLIs, or integrations that can’t negotiate a modern TLS connection.
This is intentional. We won’t hold back security updates in the hardened image to keep older clients working.
If you choose the hardened image, plan to:
- Upgrade Octopus Server regularly, so you pick up base image and library fixes.
- Keep your Tentacles, workers, and deployment targets on supported, up-to-date operating systems and Tentacle versions.
- Read the release notes for each upgrade and test it in a non-production instance before upgrading production.
If you need to support older clients or targets that can’t keep up, use the standard Octopus Server Linux Container instead.
Choose an image
| Standard image | Hardened image | |
|---|---|---|
| Base image | Debian-based .NET runtime image | Docker Hardened Image (static), no shell |
| Runs as | root |
UID 1001, group 0 (or any UID in group 0) |
| Built-in worker | Supported | Not supported |
| Script Console targeting the Octopus Server | Supported | Not supported |
| Docker-in-Docker for execution containers on the built-in worker | Supported, needs --privileged |
Not supported |
Interactive troubleshooting with docker exec |
Supported | Not supported |
| Architecture | linux |
linux |
| Security update policy | Balances updates with compatibility | Takes the latest security changes, even when they break older clients |
Getting started
The hardened image is published to the same Docker Hub repository as the standard image, using tags with a -hardened suffix.
docker run --detach --name OctopusDeploy \
--publish 8080:8080 \
--publish 10943:10943 \
--env ACCEPT_EULA="Y" \
--env DB_CONNECTION_STRING="..." \
--env ADMIN_USERNAME="admin" \
--env ADMIN_PASSWORD="..." \
--volume ./masterKey:/masterKey \
octopusdeploy/octopusdeploy:<version>-hardened- You don’t need
--interactiveor--privileged. The container runs Octopus Server in non-interactive mode and doesn’t start Docker-in-Docker. - Mounting
/masterKeyis optional. If you don’t supply aMASTER_KEY, Octopus generates one on first startup and writes it to/masterKey. You need this key for every subsequent startup against the same database./OctopusServer - If you don’t supply
ADMIN_USERNAMEandADMIN_PASSWORD, Octopus creates an admin user with default credentials and logs a warning. Change the password the first time you sign in.
Run as a non-root or arbitrary user
The image runs as UID 1001 by default. Every file and directory Octopus needs to write to is owned by group 0 and has group permissions that match the owner permissions (g=u). This means any UID that’s a member of group 0 has the same access as UID 1001.
To run as a different UID with Docker, pass --user with group 0:
docker run --detach --user 12345:0 ... octopusdeploy/octopusdeploy:<version>-hardenedOn Kubernetes, set a security context on the pod:
securityContext:
runAsNonRoot: true
runAsUser: 1001
runAsGroup: 0
fsGroup: 0On OpenShift, the default restricted security context constraint assigns a UID from the namespace’s range and adds it to group 0. You don’t need to grant the service account any additional permissions.
Volume permissions
Because the container doesn’t run as root, it can’t take ownership of mounted volumes. Make sure every volume you mount is writable by UID 1001 or by group 0.
For host directories, either change their owner to UID 1001, or give group 0 the same permissions as the owner:
sudo chown -R 1001:0 /path/to/octopus-data
sudo chmod -R g=u /path/to/octopus-dataOn Kubernetes, setting fsGroup in the pod’s security context makes supported volume types writable by group 0.
If Octopus can’t write the generated master key to a mounted /masterKey volume, startup fails with an error that explains how to fix the volume’s permissions.
Read-only root filesystem
Every path the hardened image writes to is declared as a volume, so you can run it with a read-only root filesystem, for example docker run --read-only or readOnlyRootFilesystem in a Kubernetes security context. Mount a writable volume for each path listed in volume mounts.
Configuration
The hardened image supports the same core environment variables and master key behavior as the standard image.
Environment variables
| Name | Description |
|---|---|
| ACCEPT_EULA | Must be set to Y to accept the Octopus Deploy EULA. The container won’t start without it. |
| DB_CONNECTION_STRING | Connection string to the SQL Server database. Required. |
| MASTER_KEY | The master key for an existing database. If not supplied and the database doesn’t exist, Octopus generates a new one. Required if the database already exists. |
| OCTOPUS_SERVER_BASE64_LICENSE | Your Octopus Deploy license key, base64 encoded. |
| ADMIN_USERNAME | The admin user to create. Defaults to admin. |
| ADMIN_PASSWORD | The password for the admin user. If not supplied, a default password is used. |
| ADMIN_EMAIL | The email address for the admin user. |
| ADMIN_API_KEY | An API key to create for the admin user. |
| ADMIN_EXTERNAL_ID, ADMIN_OIDCIDENTITY_NAME, ADMIN_OIDCIDENTITY_ISSUER, ADMIN_OIDCIDENTITY_SUBJECT, ADMIN_OIDCIDENTITY_AUDIENCE | When all five are set, the admin user is linked to an external OIDC identity instead of a local password. |
| OCTOPUS_SERVER_NODE_NAME | The name of this Octopus Server node. Set this to a unique value for each node in a high availability cluster. |
| OCTOPUS_SERVER_URI | The public URI of this Octopus Server. |
| TASK_CAP | The task cap for this node. Defaults to 5. |
| SSL_CERTIFICATE_FILE | Path to a certificate file inside the container. When set, Octopus binds HTTPS. The container fails to start if the file doesn’t exist. |
| SSL_CERTIFICATE_PASSWORD | The password for the certificate in SSL_CERTIFICATE_FILE. |
| HTTPS_PORT | The port to bind HTTPS on. Defaults to 443. |
| WEB_FORCE_SSL | When a certificate is supplied, set to False to keep the HTTP listener on port 8080 available alongside HTTPS. Defaults to True. |
| ENABLE_INSECURE_GRPC_LISTENER | Set to True to enable the insecure gRPC listener. |
| ENABLE_USAGE | Set to N to opt out of sending usage telemetry. |
| SERVICE_MODE | Passed to Octopus Server as --service-mode. |
DISABLE_DIND has no effect on the hardened image, because it never runs Docker-in-Docker.
Exposed container ports
| Port | Description |
|---|---|
| 8080 | Port for API and HTTP portal |
| 443 | SSL port for API and HTTP portal |
| 10943 | Port for Polling Tentacles to contact the server |
| 8443 | Port for gRPC clients to contact the server |
Volume mounts
| Name | Description | Mount source |
|---|---|---|
| /import | Imports from this folder if Octopus Migrator metadata.json exists | Host filesystem or container |
| /repository | Package path for the built-in package repository | Shared storage |
| /artifacts | Path where artifacts are stored | Shared storage |
| /taskLogs | Path where task logs are stored | Shared storage |
| /eventExports | Path where event audit logs are exported | Shared storage |
| /cache | Path where cached files, such as signature and delta files, are stored | Host filesystem or container |
| /masterKey | If mounted, Octopus writes a generated master key to /masterKey on first startup |
Host filesystem or secret store |
| /diagnostics | Startup logs and crash dumps | Host filesystem or container |
| /Octopus/.octopus | Octopus Server instance configuration | Host filesystem or container |
| /Octopus/.octopus/OctopusServer/Server | Octopus Server node configuration and logs | Host filesystem or container |
| /etc/octopus | Octopus instance registry | Host filesystem or container |
| /tmp | Temporary files | Container |
Use shared storage for files that must be shared between multiple Octopus Server nodes, such as artifacts, packages, task logs, and event exports.
Health checks
The standard image’s health check script needs bash and curl, which the hardened image doesn’t include. Instead, the hardened image has a built-in Docker HEALTHCHECK that runs:
/Octopus/Octopus.Server container-healthcheckThis command sends a request to /api. It uses HTTPS when SSL_CERTIFICATE_FILE is set, and HTTP on port 8080 otherwise. A node that’s draining or in maintenance mode is reported as healthy.
Kubernetes ignores the Docker HEALTHCHECK. Use the same command as an exec probe, or use an httpGet probe against the same endpoint:
startupProbe:
exec:
command: ["/Octopus/Octopus.Server", "container-healthcheck"]
periodSeconds: 10
failureThreshold: 30
livenessProbe:
exec:
command: ["/Octopus/Octopus.Server", "container-healthcheck"]
periodSeconds: 30
timeoutSeconds: 30Unsupported features
Because the hardened image has no shell, features that run scripts directly on the Octopus Server don’t work.
Built-in worker
The built-in worker isn’t supported. Octopus turns it off the first time a hardened container starts.
Steps configured to run on the Octopus Server fail with an error explaining that no shell is available. Configure these steps to run on an external worker or deployment target instead.
This also means execution containers can only run on external workers, not on the built-in worker.
Script Console on the Octopus Server
You can’t use the Script Console to run scripts on the Octopus Server itself. You can still use it to run scripts on deployment targets and external workers.
Switch between the standard and hardened images
You can switch an existing Octopus Server from the standard Linux image to the hardened image, and back, using the same database, volumes, and master key.
Before you switch to the hardened image:
-
Move any steps and runbooks that run on the Octopus Server to an external worker or deployment target. The built-in worker is turned off when the hardened container first starts.
-
Make sure every mounted volume is writable by UID
1001or group0. See volume permissions. -
Get your master key. You can read it from a running container with:
Bashdocker exec <container> /Octopus/Octopus.Server show-master-key --console --instance OctopusServer
Then stop the standard container and start the hardened container with the same DB_CONNECTION_STRING, MASTER_KEY, and volume mounts.
If you switch back to the standard image, the built-in worker stays turned off. You can turn it back on in Configuration ➜ Features.
Upgrading
Upgrade the hardened image the same way as the standard image: stop the running container and start a new one with the new image tag, the same environment variables, the same master key, and the same volume mounts.
Upgrade regularly. Each release picks up the latest base image and library security updates. See security updates and compatibility.
Troubleshooting
The hardened image has no shell, so you can’t open an interactive session with docker exec -it <container> bash. You can still:
- Read the container logs with
docker logs <container>orkubectl logs <pod>. - Run Octopus Server commands directly, for example
docker exec <container>./Octopus /Octopus .Server show-configuration --instance OctopusServer - Read startup logs and crash dumps from the
/diagnosticsvolume. - Attach a debug container that has its own tools, using
docker debugorkubectl debug.
If you see Permission denied errors on startup, check that your mounted volumes are writable by the container’s UID or by group 0.
For other issues, see troubleshooting Octopus Server in a container.