Sign in Start for free

Hardened Octopus Server Linux Container

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’s restricted security context constraints.
  • Contains no files with the SUID or SGID bit set, and no world-writable paths outside /tmp and /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 ... bash Supported Not supported
Architecture linux/amd64 linux/amd64
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.

Bash
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 --interactive or --privileged. The container runs Octopus Server in non-interactive mode and doesn’t start Docker-in-Docker.
  • Mounting /masterKey is optional. If you don’t supply a MASTER_KEY, Octopus generates one on first startup and writes it to /masterKey/OctopusServer. You need this key for every subsequent startup against the same database.
  • If you don’t supply ADMIN_USERNAME and ADMIN_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:

Bash
docker run --detach --user 12345:0 ... octopusdeploy/octopusdeploy:<version>-hardened

On Kubernetes, set a security context on the pod:

YAML
securityContext:
  runAsNonRoot: true
  runAsUser: 1001
  runAsGroup: 0
  fsGroup: 0

On 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:

Bash
sudo chown -R 1001:0 /path/to/octopus-data
sudo chmod -R g=u /path/to/octopus-data

On Kubernetes, setting fsGroup: 0 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: true 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/OctopusServer 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:

Bash
/Octopus/Octopus.Server container-healthcheck

This command sends a request to /api/octopusservernodes/ping. 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:

YAML
startupProbe:
  exec:
    command: ["/Octopus/Octopus.Server", "container-healthcheck"]
  periodSeconds: 10
  failureThreshold: 30
livenessProbe:
  exec:
    command: ["/Octopus/Octopus.Server", "container-healthcheck"]
  periodSeconds: 30
  timeoutSeconds: 30

Unsupported 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:

  1. 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.

  2. Make sure every mounted volume is writable by UID 1001 or group 0. See volume permissions.

  3. Get your master key. You can read it from a running container with:

    Bash
    docker 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> or kubectl 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 /diagnostics volume.
  • Attach a debug container that has its own tools, using docker debug or kubectl 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.