Add Docker support and CI workflow for Docker image publish#459
Add Docker support and CI workflow for Docker image publish#459enzofrnt wants to merge 31 commits intoBeamMP:minorfrom
Conversation
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.
|
Here is how it's look when build : To try it out : Create 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=UTCStart the container (it will automaticly download the Docker image): |
|
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 |
|
Let's do it <3 But I will not be able to try it. |
|
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 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 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.
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. |
|
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. |
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? |
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 |
Have you init submodules ? git clone --recursive <...>If you already cloned: git submodule update --init --recursiveIf 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. A simple |
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.
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) |
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? |
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.
Could you specify what implementation you mean? |
|
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 beammpYes thats totally fine! |
|
Yes, we agree ! |
What about providing both options? 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. |
|
@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. |
|
Nothing new here ?? |
WiserTixx
left a comment
There was a problem hiding this comment.
Could all french text be translated to English and could the BEAMMP_PROVIDER_DISABLE_CONFIG env var be added set to true in the compose file?
Minor cleanup across Docker-related files and CI workflow for clarity and consistency: - .dockerignore: simplified comments and ensured docs/ scripts entries are listed (keeps README.md). - .github/workflows/docker.yml: translated French comments to English for the Inspect step. - .gitignore: fixed spacing in Docker env file comment. - Dockerfile: removed an outdated comment line (no functional change to the COPY/WORKDIR steps). These are non-functional cleanup changes to improve readability and consistency.
|
@WiserTixx Regarding If the preferred approach for Docker is to use only environment variables for server config, I'd rather do it explicitly: remove the configuration volume from the compose file and set What do you prefer: adding the variable in the compose file as requested, or switching the Docker example to env-only (no config volume + variable in the Dockerfile)? |
|
There's already an env file loaded in the compose and there's also an env example file. Environment variables take priority over the config so there is no point in having both. env's are imo much easier to work with compared to mounts or editing the container, so let's go with env only. However because the env vars are registered in the compose it makes more sense to have the disable config var also in there. That way the user can still switch to a mounted config without editing the image. |
|
Ok I will do so. |
Update .env.example to include BEAMMP_PROVIDER_DISABLE_CONFIG so the provider config can be disabled via env. Simplify compose.yaml by removing the ./config volume mount and the explicit TZ environment entry while keeping the Resources volume and .env file reference.
|
Done : ) |
Remove the bind mount that mounted ./Resources into /app/Resources in compose.yaml. This stops the host Resources directory from being injected into the container (avoiding accidental overrides, permission/sync issues). Ports and env_file entries are unchanged; if runtime resources are still required, consider baking them into the image or adding a controlled volume.
Add a top-line comment to .env.example that points to the official BeamMP Server environment variable documentation (https://docs.beammp.com/server/manual/#env). This helps users locate authoritative guidance for configuring environment variables.
Remove BEAMMP_PROVIDER_DISABLE_CONFIG from .env.example and add it to the service environment in compose.yaml so the container receives the variable explicitly. .env.example retains BEAMMP_LOG_CHAT=true. This ensures the provider disable flag is injected at runtime via Docker Compose rather than left in the example env file.
Drop the fixed CMD ("--config=/config/ServerConfig.toml") from the Dockerfile, leaving only the ENTRYPOINT. This allows callers to pass runtime arguments or override the command without being forced to use the baked-in config flag.
This reverts commit 9066648.
Remove a redundant comment above the Resources volume mount in compose.yaml. This is a non-functional cleanup to reduce clutter in the compose file; the actual volume mapping (./Resources:/app/Resources) is unchanged.
|
Everythings done |
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.dockerignorehandling. Additionally, it refines the Git submodule logic in CMake for more robust and context-aware submodule management.Docker support and automation:
Dockerfileto build and package the server and its dependencies, including cache optimizations, debug symbol handling, and a non-root runtime user.compose.yamlfor local development and deployment, mapping configuration, resources, and data directories, and exposing necessary ports..github/workflows/docker.ymlto automate Docker builds for both amd64 and arm64 architectures, supporting branch and tag triggers, and pushing images to GitHub Container Registry..dockerignorefile to exclude unnecessary files and directories from Docker build context, improving build performance and reducing image size.Build system improvements:
cmake/Git.cmaketo update submodules only when in a real Git checkout, provide clearer status messages, and avoid errors when Git or.gitis 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.