Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 30 additions & 82 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
@@ -1,91 +1,39 @@
name: Docker
name: Publish Docker Image to GHCR

on:
push:
branches: [ "master" ]
# Publish semver tags as releases.
tags: [ 'v*.*.*' ]
branches:
- main
pull_request:
branches: [ "master" ]

env:
# Use docker.io for Docker Hub if empty
REGISTRY: ghcr.io
# github.repository as <account>/<repo>
IMAGE_NAME: ${{ github.repository }}


jobs:
Comment on lines 7 to 9
build:

runs-on: ubuntu-latest
permissions:
contents: read
packages: write
# This is used to complete the identity challenge
# with sigstore/fulcio when running outside of PRs.
id-token: write

test:
name: Start script tests
runs-on: ubuntu-24.04
steps:
- name: Checkout repository
- uses: actions/checkout@v4
- run: bash test/install_or_update_test.sh

build-and-publish:
name: Build and Publish Docker Image
needs: test
# PRs run the tests only; the image is published from main.
if: github.event_name == 'push'
runs-on: ubuntu-24.04
steps:
- name: Checkout repo
uses: actions/checkout@v4

# Install the cosign tool except on PR
# https://github.com/sigstore/cosign-installer
- name: Install cosign
if: github.event_name != 'pull_request'
uses: sigstore/cosign-installer@v3.3.0
with:
cosign-release: 'v2.2.2' # optional

# Set up BuildKit Docker container builder to be able to build
# multi-platform images and export cache
# https://github.com/docker/setup-buildx-action
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@master # v3.0.0

# Login against a Docker registry except on PR
# https://github.com/docker/login-action
- name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request'
uses: docker/login-action@master # v3.0.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@master # v5.0.0
- name: Login to GitHub Container Registry
# this step requires a GH_TOKEN with read:packages write:packages and delete:packages
# tested with a classic token with the above permissions
Comment on lines +27 to +28
uses: docker/login-action@v3
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@master # v5.0.0
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

# Sign the resulting Docker image digest except on PRs.
# This will only write to the public Rekor transparency log when the Docker
# repository is public to avoid leaking data. If you would like to publish
# transparency data even for private images, pass --force to cosign below.
# https://github.com/sigstore/cosign
- name: Sign the published Docker image
if: ${{ github.event_name != 'pull_request' }}
env:
# https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable
TAGS: ${{ steps.meta.outputs.tags }}
DIGEST: ${{ steps.build-and-push.outputs.digest }}
# This step uses the identity token to provision an ephemeral certificate
# against the sigstore community Fulcio instance.
run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST}
registry: ghcr.io
username: ${{github.actor}}
password: ${{secrets.GITHUB_TOKEN}}
- name: Build and Publish Base Image
run: |
docker build . --tag ghcr.io/compscidr/rust-server:$(date +%m-%d-%Y)
docker tag ghcr.io/compscidr/rust-server:$(date +%m-%d-%Y) ghcr.io/compscidr/rust-server:latest
docker push ghcr.io/compscidr/rust-server:$(date +%m-%d-%Y)
docker push ghcr.io/compscidr/rust-server:latest
Comment on lines +35 to +39
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.idea
sample.txt
rust_data*/
scheduler_app/node_modules/
Expand Down Expand Up @@ -44,3 +45,5 @@ man/man5
man/man8
pyenv
vendor/pkg/

rust-server
19 changes: 11 additions & 8 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
FROM didstopia/base:nodejs-12-steamcmd-ubuntu-18.04
# nodejs-22-steamcmd-ubuntu-24.04 (Ubuntu 24.04, glibc 2.39, node 22) pinned by digest
FROM didstopia/base:nodejs-22-steamcmd-ubuntu-24.04@sha256:ae8f7bdab405f669eadcc7178627dd47def015e22227247547904b22156f5a47

LABEL maintainer="Didstopia <support@didstopia.com> & Zaroxh <admin@rusttilldawn.eu>"
LABEL org.opencontainers.image.source="https://github.com/compscidr/rust-server"
Comment on lines +1 to +4

# Fix apt-get warnings
ARG DEBIAN_FRONTEND=noninteractive
Expand Down Expand Up @@ -35,27 +36,27 @@ RUN mkdir -p /steamcmd/rust /usr/share/nginx/html /var/log/nginx
# Setup proper shutdown support
ADD shutdown_app/ /app/shutdown_app/
WORKDIR /app/shutdown_app
RUN npm install
RUN npm ci

# Setup restart support (for update automation)
ADD restart_app/ /app/restart_app/
WORKDIR /app/restart_app
RUN npm install
RUN npm ci

# Setup scheduling support
ADD scheduler_app/ /app/scheduler_app/
WORKDIR /app/scheduler_app
RUN npm install
RUN npm ci

# Setup scheduling support
ADD heartbeat_app/ /app/heartbeat_app/
WORKDIR /app/heartbeat_app
RUN npm install
RUN npm ci

# Setup rcon command relay app
ADD rcon_app/ /app/rcon_app/
WORKDIR /app/rcon_app
RUN npm install
RUN npm ci
RUN ln -s /app/rcon_app/app.js /usr/bin/rcon

# Add the steamcmd installation script
Expand Down Expand Up @@ -124,7 +125,9 @@ ENV CHOWN_DIRS "/app,/steamcmd,/usr/share/nginx/html,/var/log/nginx"
# VOLUME [ "/steamcmd/rust" ]

# Container Healthcheck
HEALTHCHECK --start-period=15m --retries=1 CMD /bin/bash -c 'if rcon test | grep -q "RconApp::Error"; then exit 1; else exit 0; fi'
# rcon reads RUST_RCON_PORT/RUST_RCON_PASSWORD from the environment, but the real
# values may live in /etc/rust/rust.env (sourced by start.sh), so source it here too
HEALTHCHECK --start-period=15m --retries=1 CMD /bin/bash -c 'set -a; [ -f /etc/rust/rust.env ] && source /etc/rust/rust.env; set +a; if rcon test | grep -q "RconApp::Error"; then exit 1; else exit 0; fi'

# Start the server
CMD [ "bash", "/app/start.sh"]
38 changes: 36 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
# Rust server that runs inside a Docker container
Fork of [Didstopia/rust-server](https://github.com/Didstopia/rust-server)
Fork of [zaroxh/rust-server](https://github.com/zaroxh/rust-server) and [Didstopia/rust-server](https://github.com/Didstopia/rust-server)

This fork adds a healthcheck and more environment variables to set (see below for a full list).

It also lets you override the environment variables with an .env file on
the docker volume. This has made it easier for me to manage wipe seeds with
a cron job on the host that changes the .env value and restarts the container.
I found if you run containers via ansible and pass the .env file, restarting
the container will keep the same old values from when it was originally run.
This setup allows overriding anything passed in from docker. The config file
is located at `/etc/rust/rust.env` in the container. You can map a volume
locally to the file for peristence.

I also split the seed into its own .env file at `/etc/rust/seed.env` to make
it easier to update configs without affecting the map.

For an example see the `docker-compose.yml` file.

## docker-compose.yml
```
services:
rust-server:
container_name: rust-server
image: ghcr.io/zaroxh/rust-server:master
image: ghcr.io/compscidr/rust-server
restart: unless-stopped
ports:
- 28015:28015/tcp
Expand All @@ -21,6 +35,7 @@ services:
- 28082:28082/udp
volumes:
- ./rust-server/:/steamcmd/rust
- ./rust.env:/etc/rust/rust.env
environment:
- RUST_SERVER_IDENTITY=rust-server
- RUST_SERVER_NAME=[EU] Rust Server running in Docker Container
Expand Down Expand Up @@ -58,8 +73,27 @@ RUST_BRANCH (DEFAULT: Not set - Sets the branch argument to use, eg. set to "-be
RUST_UPDATE_CHECKING (DEFAULT: "0" - Set to 1 to enable fully automatic update checking, notifying players and restarting to install updates)
RUST_UPDATE_BRANCH (DEFAULT: "public" - Set to match the branch that you want to use for updating, ie. "prerelease" or "public", but do not specify arguments like "-beta")
RUST_START_MODE (DEFAULT: "0" - Determines if the server should update and then start (0), only update (1) or only start (2))
STEAMCMD_RETRY_DELAY (DEFAULT: "60" - Seconds to wait before exiting when the boot-time game update fails; see below)
RUST_OXIDE_ENABLED (DEFAULT: "0" - Set to 1 to automatically install the latest version of Oxide)
RUST_OXIDE_UPDATE_ON_BOOT (DEFAULT: "1" - Set to 0 to disable automatic update of Oxide on boot)
RUST_RCON_SECURE_WEBSOCKET (DEFAULT: "0" - Set to 1 to enable secure websocket connections to the RCON web interface)
RUST_HEARTBEAT (DEFAULT: "0" - Set to 1 to enable the heartbeat service which will forcibly quit the server if it becomes unresponsive to queries)
```

## When the game update fails at boot

The container updates Rust with steamcmd on every start. steamcmd exits 0 even when the
update failed — on 2026-09-03 it printed `Error! App '258550' state is 0x486 after update
job.` because Facepunch's monthly depot was mid-rollout at the moment the server restarted.
The old script carried on, installed an Oxide built for the new Rust over the old one, and
`RustDedicated` hung at boot. A hang is not an exit, so `restart: unless-stopped` never
fired, and the server stayed down until someone noticed.

Now the start script accepts only steamcmd's own `Success! App '258550' fully installed`
line. Anything else waits `STEAMCMD_RETRY_DELAY` seconds and exits 1 before Oxide is
touched, so a container restart policy (`unless-stopped` or `always`) retries until Steam
serves the update. Restarting a wipe or maintenance window into the first minutes of a
Facepunch release now costs a few restart loops instead of an outage.

If you run the game with your own wrapper instead of this image, it needs the same rule:
a failed update must exit non-zero, not launch the stale build.
20 changes: 20 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
services:
rust-server:
container_name: rust-server
build:
context: .
dockerfile: Dockerfile
restart: unless-stopped
ports:
- 26015:28015/tcp
- 26015:28015/udp
- 26016:28016/tcp
- 26016:28016/udp
- 26017:28017/tcp
- 26017:28017/udp
- 26082:28082/tcp
- 26082:28082/udp
volumes:
- ./rust-server/:/steamcmd/rust
- ./rust.env:/etc/rust/rust.env
- ./seed.env:/etc/rust/seed.env
27 changes: 8 additions & 19 deletions heartbeat_app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 19 additions & 10 deletions rcon_app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading