Skip to content

Conversation

@enzofrnt
Copy link

This pull request introduces comprehensive Docker support for the project, enabling streamlined containerized builds, testing, and deployment. It adds a multi-stage Dockerfile, a Docker Compose configuration, a GitHub Actions workflow for automated multi-architecture image builds, and improves .dockerignore handling. Additionally, it refines the Git submodule logic in CMake for more robust and context-aware submodule management.

Docker support and automation:

  • Added a multi-stage Dockerfile to build and package the server and its dependencies, including cache optimizations, debug symbol handling, and a non-root runtime user.
  • Introduced compose.yaml for local development and deployment, mapping configuration, resources, and data directories, and exposing necessary ports.
  • Added .github/workflows/docker.yml to automate Docker builds for both amd64 and arm64 architectures, supporting branch and tag triggers, and pushing images to GitHub Container Registry.
  • Created a .dockerignore file to exclude unnecessary files and directories from Docker build context, improving build performance and reducing image size.

Build system improvements:

  • Enhanced cmake/Git.cmake to update submodules only when in a real Git checkout, provide clearer status messages, and avoid errors when Git or .git is not present. (Which can append in Docker build)

By creating this pull request, I understand that code that is AI generated or otherwise automatically generated may be rejected without further discussion.
I declare that I fully understand all code I pushed into this PR, and wrote all this code myself and own the rights to this code.

Introduces Dockerfile, .dockerignore, and compose.yaml for containerization and deployment. Adds a GitHub Actions workflow for automated Docker builds and pushes. Updates .gitignore to exclude build artifacts.
The GitHub Actions workflow will now run on pushes to the docker-support branch, enabling CI/CD for Docker-related changes.
Improves Dockerfile with multi-stage builds, BuildKit caching, and more granular dependency installation. Updates GitHub Actions workflow to use registry-based build cache. Refines .dockerignore and compose.yaml for better Docker context and volume management. Enhances CMake Git submodule logic to avoid errors outside git checkouts.
Moves IMAGE_NAME definition from env to a workflow step, setting it to the lowercased repository name using bash. This improves flexibility and ensures consistent image naming.
Changed the Docker GitHub Actions workflow to set IMAGE_NAME using the @l parameter expansion and store it in GITHUB_ENV instead of GITHUB_OUTPUT. Also added a step to echo the IMAGE_NAME environment variable.
Changed the COPY source from /work/build-server/BeamMP-Server to /work/out/BeamMP-Server to reflect the new build artifact location. This ensures the final artifact is correctly copied during the Docker build process.
Refactored the GitHub Actions workflow to separate Docker image builds for amd64 and arm64 architectures into distinct jobs. This improves build clarity and allows for targeted platform builds on appropriate runners.
Consolidated separate amd64 and arm64 jobs into a single matrix-based 'build' job for multi-architecture Docker image builds. This simplifies the workflow, reduces duplication, and ensures both architectures are built consistently. Also added recursive submodule checkout and standardized registry/image references.
Bump the docker/build-push-action version from v5 to v6 in the GitHub Actions workflow to use the latest features and improvements.
Configured the GitHub Actions Docker workflow to use registry-based build cache with cache-from and cache-to options. This should improve build times by leveraging cached layers across builds.
Added 'provenance: false' and 'sbom: false' to the Docker build step in the GitHub Actions workflow to prevent generation of provenance and SBOM artifacts.
Removed outdated and redundant comments, including French notes, from the Dockerfile. Updated the artifact copy comment to clarify the use of BuildKit cache and artifact location.
Changed the Docker image reference from 'beammp-server:latest' to 'ghcr.io/BeamMP/beammp-server' to use the official image from GitHub Container Registry.
Cleaned up the Docker Compose file by removing unused and commented-out environment variables and resource limits for improved readability.
The GitHub Actions workflow will no longer run for pushes to the 'docker-support' branch. This streamlines workflow execution to only relevant branches and tags.
@enzofrnt
Copy link
Author

enzofrnt commented Jan 11, 2026

Here is how it's look when build :
https://github.com/enzofrnt/BeamMP-Server/pkgs/container/beammp-server
https://github.com/enzofrnt/BeamMP-Server/actions/runs/20898778316

To try it out :

Create compose.yaml file :

services:
  beammp-server:
    image: ghcr.io/enzofrnt/beammp-server:docker-support
    container_name: beammp-server
    restart: unless-stopped
    ports:
      - "30814:30814/tcp"
      - "30814:30814/udp"
    volumes:
      # Mount configuration directory
      - ./config:/config
      # Mount resources directory (mods, plugins, etc.)
      - ./resources:/resources:ro
      # Mount working directory for logs and data
      - ./data:/app/data
    environment:
      - TZ=UTC

Start the container (it will automaticly download the Docker image):
docker compose up -d

@OfficialLambdax
Copy link
Collaborator

Awesome PR! I would suggest one change and that is to install LuaRocks into the container as well. A handful of scripts require rock modules to run and many users have trouble setting it up which in return makes scripters trying to not require lua rocks. If a docker container had support for that at default then that would open up a whole new world

@OfficialLambdax OfficialLambdax added the good first issue Good for newcomers label Jan 11, 2026
@enzofrnt
Copy link
Author

Let's do it <3 But I will not be able to try it.

@enzofrnt
Copy link
Author

Can I get some Lua examples I can try, along with a brief explanation of how they work?

@OfficialLambdax
Copy link
Collaborator

Can I get some Lua examples I can try, along with a brief explanation of how they work?

Sure. Lets do an example with LuaSocket. You can install a rock like this luarocks install luasocket, just push it into a console.

Then create a folder inside the beammp servers Resources/Server folder. Name it anyway you want. In that new folder create a new file, name it main.lua. Inside it paste

local Socket = require("socket")

Reboot the server and done (:

if this throws an error the setup failed.

Updated the image name in compose.yaml from 'BeamMP' to 'beammp' to ensure consistency and avoid potential issues with case sensitivity in Docker image references.
@OfficialLambdax
Copy link
Collaborator

If you go into the container, install something, and then restart it, anything you just changed or installed will disappear unless it’s stored in a volume.

A simple container restart doesnt remove any data. Redeploying that container would do

docker compose down -- would remove!
docker compose stop -- just stops!

@enzofrnt
Copy link
Author

enzofrnt commented Jan 11, 2026

If you go into the container, install something, and then restart it, anything you just changed or installed will disappear unless it’s stored in a volume.

A simple container restart doesnt remove any data. Redeploying that container would do

docker compose down -- would remove!
docker compose stop -- just stops!

yes, my bad ^^ I havedelete my comment.

But it’s a bit tricky to install things manually like that. Some documentation should provide instructions on how to properly create your own image, so you can update it and bring containers up/down without too many issues.

@Starystars67
Copy link
Member

Furthering your discussion there of luarocks inclusion, What if as part of the entrypoint script it checks an ENV value for the modules to install via luarocks?

This way it could install them at start and (maybe?) skip them if already installed without requiring the user to have to enter the container which is great for those hosting via the likes of a game panel or hosting provider.

@toinopt
Copy link

toinopt commented Jan 11, 2026

Furthering your discussion there of luarocks inclusion, What if as part of the entrypoint script it checks an ENV value for the modules to install via luarocks?

This way it could install them at start and (maybe?) skip them if already installed without requiring the user to have to enter the container which is great for those hosting via the likes of a game panel or hosting provider.

Along with this having a ENV for the LUA version to install might be useful too.

@OfficialLambdax
Copy link
Collaborator

But it’s a bit tricky to install things manually like that. Some documentation should provide instructions on how to properly create your own image, so you can update it and bring containers up/down without too many issues.

I believe the proper way to have a rock installed is by the script that requires it. eg (havent tested)

local is_ok, Socket = pcall(require, "socket")
if not is_ok then -- if module is not present then install it
   os.execute("luarocks install luasocket") -- will block until done
   Socket = require("socket")
end

@Starystars67
Copy link
Member

But it’s a bit tricky to install things manually like that. Some documentation should provide instructions on how to properly create your own image, so you can update it and bring containers up/down without too many issues.

I believe the proper way to have a rock installed is by the script that requires it. eg (havent tested)

local is_ok, Socket = pcall(require, "socket")
if not is_ok then -- if module is not present then install it
   os.execute("luarocks install luasocket") -- will block until done
   Socket = require("socket")
end

With this, are you thinking that this would be done from a script intended to be running from the BeamMP server or as a separate install script that is run before the BeamMP server is started?

@OfficialLambdax
Copy link
Collaborator

OfficialLambdax commented Jan 11, 2026

With this, are you thinking that this would be done from a script intended to be running from the BeamMP server or as a separate install script that is run before the BeamMP server is started?

By a BeamMP server script. That way a user wanting to use a public server script can just drop it into their server and it will set itself up. Otherwise the user would have to edit the compose again. It takes a setup out of the "How to install" instructions, right? As in its more convenient

@enzofrnt
Copy link
Author

I agree about LuaRocks, but I disagree with adding any installation scripts or other kinds of setup tooling inside container for the moment.

@OfficialLambdax
Copy link
Collaborator

I agree about LuaRocks, but I disagree with adding any installation scripts or other kinds of setup tooling inside container for the moment.

Oh yeah i see the conversation got mixed up. There would be no installation scripts inside the container. The previous discussion was just about how a lua script could install its dependencies itself without the need of any installation script

@enzofrnt
Copy link
Author

enzofrnt commented Jan 11, 2026

It's seems to work. But not everything.
image
I'm not sure that the issue come from my work.

Removed the --working-directory flag from the Dockerfile CMD. Updated compose.yaml to mount resources to /app/Resources instead of /resources and removed the data volume mount.
Added a .env.example file to document environment variables. Updated .gitignore to exclude .env and build/ directories. Modified compose.yaml to use the .env file for environment variables.
@enzofrnt
Copy link
Author

enzofrnt commented Jan 11, 2026

A simple Dockerfile like this should be sufficient to install dependencies and build your own custom image. That said, I’m not sure whether the issue I’m seeing above is caused by Docker.
Working on it here : https://github.com/enzofrnt/BeamMP-Server/tree/docker-suppor-lua-rocks

FROM ghcr.io/beammp/beammp-server:latest

USER root
RUN luarocks --lua-version=5.3 install luasocket
USER beammp

* Update docker.yml

* Create docker-test.yml

* Update Docker workflow authentication to GitHub Container Registry

Changed Docker login in docker-test.yml to use GitHub Container Registry credentials instead of Docker Hub. Removed unused REGISTRY environment variable from docker.yml for consistency.

* Update docker-test.yml

* Update docker.yml

* Update docker.yml

* Refactor Docker workflow for native multi-arch builds

Removed the separate docker-test workflow and consolidated multi-architecture Docker image building into a single, improved workflow. The new workflow uses matrix builds for amd64 and arm64, explicit runners, and streamlined manifest creation, improving maintainability and native support for multi-arch images.

* Update Docker workflow name and trigger branches

Renames the workflow to 'Docker Build and Publish (native multi-arch)' and removes the 'improve-docker-workflow' branch from the push trigger.

* Add pull_request trigger to Docker workflow

The GitHub Actions workflow for Docker now runs on pull requests in addition to pushes to main, minor branches, and tags. This ensures Docker-related checks are performed for incoming pull requests.

* Update docker.yml

* Update docker.yml
* docker: add LuaRocks support

Install lua5.3/luarocks (+ build deps) in runtime image; mount /resources RW in compose example for plugin rock installs.

* Update docker.yml

* Update docker.yml

* Update docker.yml

* Add --export-dynamic linker flag to CMake builds

Updated the Dockerfile to include the -Wl,--export-dynamic linker flag in both server and test CMake build steps. This ensures that dynamic symbols are exported, which may be required for certain runtime features or debugging.
@enzofrnt
Copy link
Author

@OfficialLambdax The container can now easily support Lua scripting.
Could you share a simple script I can try out?

The 'docker-suppor-lua-rocks' branch was removed from the workflow trigger list, likely because it is no longer in use or relevant for Docker build and publish actions.
@enzofrnt
Copy link
Author

enzofrnt commented Jan 12, 2026

Example of modification to add Lua dependencies:

FROM ghcr.io/beammp/beammp-server:<tag>

USER root
RUN luarocks --lua-version=5.3 install luasocket
USER beammp

@OfficialLambdax
Copy link
Collaborator

OfficialLambdax commented Jan 12, 2026

On my end the build of the image fails with

failed to solve: process "/bin/sh -c mkdir -p /work/out &&     cmake -S /work -B /work/build-server -G Ninja       -DCMAKE_TOOLCHAIN_FILE=/work/vcpkg/scripts/buildsystems/vcpkg.cmake       -DCMAKE_BUILD_TYPE=Release       -DCMAKE_EXE_LINKER_FLAGS=\"-Wl,--export-dynamic\"       -DCMAKE_CXX_FLAGS=\"-O3 -g -Wl,-z,norelro -Wl,--hash-style=gnu -Wl,-z,noseparate-code -ffunction-sections -fdata-sections -Wl,--gc-sections\"       -DBeamMP-Server_ENABLE_LTO=${ENABLE_LTO}     && cmake --build /work/build-server --parallel ${BUILD_PARALLEL} -t BeamMP-Server     && objcopy --only-keep-debug /work/build-server/BeamMP-Server /work/build-server/BeamMP-Server.debug     && strip --strip-unneeded /work/build-server/BeamMP-Server     && objcopy --add-gnu-debuglink=/work/build-server/BeamMP-Server.debug /work/build-server/BeamMP-Server     && install -m 0755 /work/build-server/BeamMP-Server /work/out/BeamMP-Server" did not complete successfully: exit code: 1

@enzofrnt
Copy link
Author

On my end the build of the image fails with

failed to solve: process "/bin/sh -c mkdir -p /work/out &&     cmake -S /work -B /work/build-server -G Ninja       -DCMAKE_TOOLCHAIN_FILE=/work/vcpkg/scripts/buildsystems/vcpkg.cmake       -DCMAKE_BUILD_TYPE=Release       -DCMAKE_EXE_LINKER_FLAGS=\"-Wl,--export-dynamic\"       -DCMAKE_CXX_FLAGS=\"-O3 -g -Wl,-z,norelro -Wl,--hash-style=gnu -Wl,-z,noseparate-code -ffunction-sections -fdata-sections -Wl,--gc-sections\"       -DBeamMP-Server_ENABLE_LTO=${ENABLE_LTO}     && cmake --build /work/build-server --parallel ${BUILD_PARALLEL} -t BeamMP-Server     && objcopy --only-keep-debug /work/build-server/BeamMP-Server /work/build-server/BeamMP-Server.debug     && strip --strip-unneeded /work/build-server/BeamMP-Server     && objcopy --add-gnu-debuglink=/work/build-server/BeamMP-Server.debug /work/build-server/BeamMP-Server     && install -m 0755 /work/build-server/BeamMP-Server /work/out/BeamMP-Server" did not complete successfully: exit code: 1

How do you build it ?

@OfficialLambdax
Copy link
Collaborator

How do you build it ?

I took a copy of your branch and then simply docker compose up

@enzofrnt
Copy link
Author

enzofrnt commented Jan 12, 2026

How do you build it ?

I took a copy of your branch and then simply docker compose up

Have you init submodules ?

git clone --recursive <...>

If you already cloned:

git submodule update --init --recursive

If you prefer, the image is available here:

docker pull ghcr.io/enzofrnt/beammp-server:pr-2

dockerfile: Dockerfile
image: ghcr.io/beammp/beammp-server
container_name: beammp-server
restart: unless-stopped
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider adding the bridge network as well. There is rarely a need for a simple container to have its own network

network_mode: bridge

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appreciate the suggestion, but I prefer to stick to the default Docker Compose behavior.

While it might seem like overkill for a single container, the default Compose network provides better isolation than the shared docker0 bridge. Also, ùusing network_mode: bridgeù disables the embedded DNS server, which would prevent service discovery if anyone ever decide to add a second container (like a database or a dashboard) to this stack later.

@OfficialLambdax
Copy link
Collaborator

If you prefer, the image is available here:

docker pull ghcr.io/enzofrnt/beammp-server:pr-2

Yes that helps. Im on a slow machine atm and just to get to the error took me a hour. This helps.
As expected the dirs the container creates are root owned and the server itself is even not able to write to them

beammp-server  | [12/01/26 11:45:15] [INFO] Custom config requested via commandline arguments: '/config/ServerConfig.toml'
beammp-server  | [12/01/26 11:45:15] [INFO] No config file found! Generating one...
beammp-server  | [12/01/26 11:45:15] [ERROR] Failed to create/write to config file: Permission denied
beammp-server  | [12/01/26 11:45:15] [ERROR] A fatal exception has occurred and the server is forcefully shutting down.
beammp-server  | [12/01/26 11:45:15] [ERROR] Failed to create/write to config file

A simple chown fixed it, but just for your info

Set default values for BeamMP environment variables in .env.example to provide clearer configuration guidance for new users.
Replaced hardcoded port values with the BEAMMP_PORT environment variable in the Docker Compose file to allow configurable port mapping for the beammp-server service.
@enzofrnt
Copy link
Author

enzofrnt commented Jan 12, 2026

If you prefer, the image is available here:

docker pull ghcr.io/enzofrnt/beammp-server:pr-2

Yes that helps. Im on a slow machine atm and just to get to the error took me a hour. This helps. As expected the dirs the container creates are root owned and the server itself is even not able to write to them

beammp-server  | [12/01/26 11:45:15] [INFO] Custom config requested via commandline arguments: '/config/ServerConfig.toml'
beammp-server  | [12/01/26 11:45:15] [INFO] No config file found! Generating one...
beammp-server  | [12/01/26 11:45:15] [ERROR] Failed to create/write to config file: Permission denied
beammp-server  | [12/01/26 11:45:15] [ERROR] A fatal exception has occurred and the server is forcefully shutting down.
beammp-server  | [12/01/26 11:45:15] [ERROR] Failed to create/write to config file

A simple chown fixed it, but just for your info

Hmm, I’m not able to reproduce it. I don’t really understand why you’re running into this issue, are you running the container as root user on your local device?

@OfficialLambdax
Copy link
Collaborator

Hmm, I’m not able to reproduce it. I don’t really understand why you’re running into this issue, are you running the container as root user on your local device?

Your right that was my bad. I ran it with sudo. In the meantime ive played around with luarocks in this container. Where as a rock isnt installed in the image but via any script that requires it at runtime. This here is a working solution ive came up with

local function addLocalLuaRocksToPackagPath()
    local username = io.popen("whoami"):read()
    local path = string.format('/home/%s/.luarocks/share/lua/5.3/?.lua', username)
    local cpath = string.format('/home/%s/.luarocks/lib/lua/5.3/?.so', username)

    if not package.path:find(username .. '/.luarocks') then
        package.path = package.path .. ';' .. path
    end
    if not package.cpath:find(username .. '/.luarocks') then
        package.cpath = package.cpath .. ';' .. cpath
    end
end

local function requireRock(name, package_name)
    local is_ok, rock = pcall(require, name)
    if not is_ok then
        os.execute('luarocks install --local ' .. package_name)
        rock = require(name)
    end
    return rock
end

addLocalLuaRocksToPackagPath() -- called on state init

local Socket = requireRock("socket", "luasocket") -- try require, if fail then install
print(Socket)

@enzofrnt
Copy link
Author

enzofrnt commented Jan 12, 2026

In the meantime ive played around with luarocks in this container. Where as a rock isnt installed in the image but via any script that requires it at runtime. This here is a working solution ive came up with

I'm not sure that's the best approach. I feel that installing packages on the fly isn't really a best practice; it seems much more robust to install them cleanly once during the build process.

That said, it's open for debate. We should see what the project maintainers think and which method they prefer to define as the 'standard' moving forward.

I do admit, however, that your method could effectively work within the current image. It might be a handy solution for 'Docker noobies' (beginners) who aren't comfortable building their own custom images.

By the way, do you understand how my current implementation works?

@OfficialLambdax
Copy link
Collaborator

OfficialLambdax commented Jan 12, 2026

I'm not sure that's the best approach. I feel that installing packages on the fly isn't really a best practice; it seems much more robust to install them cleanly once during the build process.

The idea is that a user of the image can just drag and drop a script they sourced from a third party into their server and it install the dependencies it needs itself. Without the creator having to explain that the user must now clone the repo, edit the dockerfile and build it themselfs. No it will just work out of the box. That makes it very convenient for beginners and advanced users alike.

If the image forced them to install rocks during the build process then anytime the user would want to use a new rock theyd have to update the entire image and recreate all containers using it. That be rather in the way then be productive.

By the way, do you understand how my current implementation works?

Could you specify what implementation you mean?

@enzofrnt
Copy link
Author

That is actually how Docker is supposed to work. It's the standard practice to ensure stability. Beginners can indeed use your proposition for ease of use, but this doesn't mean we shouldn't provide a solution to do it 'the right way' for others. By 'my implementation', I meant the Dockerfile structure I proposed earlier by adding dependecies in custom Dockerfile to make your own image.

@OfficialLambdax
Copy link
Collaborator

That is actually how Docker is supposed to work. It's the standard practice to ensure stability. Beginners can indeed use your proposition for ease of use, but this doesn't mean we shouldn't provide a solution to do it 'the right way' for others. By 'my implementation', I meant the Dockerfile structure I proposed earlier by adding dependecies in custom Dockerfile to make your own image.

Yes its the standard to install everything during the build process, i just want to respect that most users dont know how to set things up and make it as convenient as possible for users to install server side mods. And its just very convenient to make on the fly changes (drag, drop, restart, done - compared to fully setting everything up again after one change).

You ment this?

FROM ghcr.io/beammp/beammp-server:latest

USER root
RUN luarocks --lua-version=5.3 install luasocket
USER beammp

Yes thats totally fine!

@enzofrnt
Copy link
Author

Yes, we agree !

@toinopt
Copy link

toinopt commented Jan 12, 2026

That is actually how Docker is supposed to work. It's the standard practice to ensure stability. Beginners can indeed use your proposition for ease of use, but this doesn't mean we shouldn't provide a solution to do it 'the right way' for others. By 'my implementation', I meant the Dockerfile structure I proposed earlier by adding dependecies in custom Dockerfile to make your own image.

What about providing both options?
One is the gold image with just the basics without lua and another is the noobie version.

I say this a someone that that to learn how to build docker images by trial and error pretty much on my own, at one point I even gave up because I couldn't figure out all the LUA modules needed.

I understand how much easier it would be for a mod developer to just have a small instructions basically being, use this noobie image and add the files to the correct folder and it will install everything it needs on its own.

Building a new image is a non-trivial step that requires learning a lot of stuff and ideally have a CI-CD system.

@enzofrnt
Copy link
Author

@toinopt I said: “Beginners can indeed use your proposal for ease of use, but that doesn’t mean we shouldn’t provide a solution to do it ‘the right way’ for others.”

To clarify: the current image contains Luarock and Lua and can be modified by someone who has the necessary skills ("actually how Docker is supposed to work"). More beginner users can also use the solution proposed by Lambdax. Both will work in the current state of the pull request, and personally I don’t want to change it.

Only the maintainers of BeamMP-Server can say whether they want modifications or not.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

good first issue Good for newcomers

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants