-
-
Notifications
You must be signed in to change notification settings - Fork 77
Add Docker support and CI workflow for Docker image publish #459
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: minor
Are you sure you want to change the base?
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 |
|
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 |
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.
|
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. 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.
|
@OfficialLambdax The container can now easily support Lua scripting. |
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.
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 |
|
On my end the build of the image fails with |
How do you build it ? |
I took a copy of your branch and then simply |
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 |
| dockerfile: Dockerfile | ||
| image: ghcr.io/beammp/beammp-server | ||
| container_name: beammp-server | ||
| restart: unless-stopped |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
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. |

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.