Merge pull request #2454 from rommapp/secure-docker-image

[ROMM-2432] Secure docker image
This commit is contained in:
Georges-Antoine Assi
2025-09-23 08:43:44 -04:00
committed by GitHub
10 changed files with 86 additions and 57 deletions

View File

@@ -42,22 +42,22 @@ jobs:
run: echo "Triggered by ${{ github.event_name }}"
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v4.3.0
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@v3.6.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v3.11.1
- name: Login to Docker Hub
uses: docker/login-action@v3
uses: docker/login-action@v3.5.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
uses: docker/login-action@v3.5.0
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -65,7 +65,7 @@ jobs:
- name: Generate Docker metadata (slim)
id: meta-slim
uses: docker/metadata-action@v5
uses: docker/metadata-action@v5.8.0
with:
images: |
name=rommapp/romm
@@ -85,7 +85,7 @@ jobs:
- name: Generate Docker metadata (full)
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@v5.8.0
with:
images: |
name=rommapp/romm
@@ -106,7 +106,7 @@ jobs:
- name: Build slim image
id: build-slim
uses: docker/build-push-action@v6
uses: docker/build-push-action@v6.18.0
with:
file: docker/Dockerfile
context: .
@@ -118,7 +118,7 @@ jobs:
- name: Build full image
id: build-full
uses: docker/build-push-action@v6
uses: docker/build-push-action@v6.18.0
with:
file: docker/Dockerfile
context: .

View File

@@ -13,10 +13,10 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v4.3.0
- name: Set up Python 3.13
uses: actions/setup-python@v5
uses: actions/setup-python@v6.0.0
with:
python-version: "3.13"

View File

@@ -31,7 +31,7 @@ jobs:
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v4.3.0
- name: Install mariadb connectors
run: |
@@ -39,7 +39,7 @@ jobs:
sudo apt-get install -y libmariadb3 libmariadb-dev
- name: Install uv
uses: astral-sh/setup-uv@v5
uses: astral-sh/setup-uv@v6.7.0
- name: Install python
run: |
@@ -62,7 +62,7 @@ jobs:
uv run pytest -vv --maxfail=10 --junitxml=pytest-report.xml --cov --cov-report xml:coverage.xml --cov-config=.coveragerc .
- name: Publish test results
uses: EnricoMi/publish-unit-test-result-action/linux@v2
uses: EnricoMi/publish-unit-test-result-action/linux@sha-3a74b29
if: (!cancelled())
with:
files: |

View File

@@ -26,26 +26,26 @@ jobs:
run: echo "Triggered by ${{ github.event_name }}"
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v4.3.0
with:
ref: ${{ github.event.inputs.branch }}
fetch-depth: 0
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@v3.6.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v3.11.1
- name: Login to Docker Hub
uses: docker/login-action@v3
uses: docker/login-action@v3.5.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Generate Docker metadata
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@v5.8.0
with:
images: |
name=rommapp/romm-testing
@@ -54,7 +54,7 @@ jobs:
- name: Build full image
id: build-full
uses: docker/build-push-action@v6
uses: docker/build-push-action@v6.18.0
with:
file: docker/Dockerfile
context: .

View File

@@ -17,6 +17,6 @@ jobs:
contents: read # For repo checkout
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v4.3.0
- name: Trunk Check
uses: trunk-io/trunk-action@v1
uses: trunk-io/trunk-action@v1.2.4

View File

@@ -20,10 +20,10 @@ jobs:
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v4.3.0
- name: Set up Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v5.0.0
with:
node-version: "18"

View File

@@ -5,7 +5,7 @@ runs:
using: composite
steps:
- name: Setup node
uses: actions/setup-node@v4
uses: actions/setup-node@v5.0.0
with:
node-version: 18

View File

@@ -11,26 +11,39 @@
# - dev-slim: Slim image with development dependencies
# - dev-full: Full image with emulator stage and development dependencies
# Versions:
# ARGUMENT DECLARATIONS
ARG ALPINE_VERSION=3.22
ARG NGINX_VERSION=1.29.0
ARG NODE_VERSION=20.19
ARG ALPINE_SHA256=4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8c44de311d1
ARG PYTHON_VERSION=3.13
# Alias stages:
FROM python:${PYTHON_VERSION}-alpine${ALPINE_VERSION} AS python-alias
ARG PYTHON_ALPINE_SHA256=9ba6d8cbebf0fb6546ae71f2a1c14f6ffd2fdab83af7fa5669734ef30ad48844
ARG NODE_VERSION=20.19
ARG NODE_ALPINE_SHA256=eabac870db94f7342d6c33560d6613f188bbcf4bbe1f4eb47d5e2a08e1a37722
ARG NGINX_VERSION=1.29.1
ARG NGINX_SHA256=42a516af16b852e33b7682d5ef8acbd5d13fe08fecadc7ed98605ba5e3b26ab8
ARG UV_VERSION=0.7.19
ARG UV_SHA256=9ce16aa2fe33496c439996865dc121371bb33fd5fb37500007d48e2078686b0d
FROM node:${NODE_VERSION}-alpine${ALPINE_VERSION} AS frontend-build
FROM python:${PYTHON_VERSION}-alpine${ALPINE_VERSION}@sha256:${PYTHON_ALPINE_SHA256} AS python-alias
# FRONTEND BUILD
FROM node:${NODE_VERSION}-alpine${ALPINE_VERSION}@sha256:${NODE_ALPINE_SHA256} AS frontend-build
WORKDIR /front
COPY ./frontend/package*.json ./
RUN npm ci
RUN npm ci --ignore-scripts --no-audit --no-fund
COPY ./frontend ./
RUN npm run build
# https://github.com/astral-sh/uv/pkgs/container/uv/452595714
FROM ghcr.io/astral-sh/uv:${UV_VERSION}-python${PYTHON_VERSION}-alpine@sha256:${UV_SHA256} AS uv-stage
# BACKEND PYTHON BUILD
FROM python-alias AS backend-build
# git is needed to install streaming-form-data fork
@@ -43,14 +56,13 @@ RUN apk add --no-cache \
mariadb-connector-c-dev \
musl-dev
COPY --from=ghcr.io/astral-sh/uv:0.7.19 /uv /uvx /bin/
COPY --from=uv-stage /usr/local/bin/uv /usr/local/bin/uvx /bin/
WORKDIR /src
COPY ./pyproject.toml ./uv.lock /src/
RUN uv sync --locked --no-cache
FROM backend-build AS backend-dev-build
# linux-headers is needed to install psutil
@@ -60,8 +72,8 @@ RUN apk add --no-cache \
RUN uv sync --locked --no-cache --all-extras
FROM alpine:${ALPINE_VERSION} AS rahasher-build
# CUSTOM RAHASHER FOR RETROACHIEVEMENTS
FROM alpine:${ALPINE_VERSION}@sha256:${ALPINE_SHA256} AS rahasher-build
RUN apk add --no-cache \
g++ \
git \
@@ -84,24 +96,34 @@ RUN git clone --recursive --branch "${RALIBRETRO_VERSION}" --depth 1 https://git
make HAVE_CHD=1 -f ./Makefile.RAHasher
FROM alpine:${ALPINE_VERSION} AS emulator-stage
# FETCH EMULATORJS AND RUFFLE
FROM alpine:${ALPINE_VERSION}@sha256:${ALPINE_SHA256} AS emulator-stage
RUN apk add --no-cache \
7zip \
wget
wget \
ca-certificates
ARG EMULATORJS_VERSION=4.2.3
ARG EMULATORJS_SHA256=07d451bc06fa3ad04ab30d9b94eb63ac34ad0babee52d60357b002bde8f3850b
RUN wget "https://github.com/EmulatorJS/EmulatorJS/releases/download/v${EMULATORJS_VERSION}/${EMULATORJS_VERSION}.7z" && \
echo "${EMULATORJS_SHA256} ${EMULATORJS_VERSION}.7z" | sha256sum -c - && \
7z x -y "${EMULATORJS_VERSION}.7z" -o/emulatorjs && \
rm -rf "${EMULATORJS_VERSION}.7z";
rm -f "${EMULATORJS_VERSION}.7z"
ARG RUFFLE_VERSION=nightly-2025-08-14
ARG RUFFLE_FILE=ruffle-nightly-2025_08_14-web-selfhosted.zip
RUN wget "https://github.com/ruffle-rs/ruffle/releases/download/${RUFFLE_VERSION}/${RUFFLE_FILE}" && \
unzip -o "${RUFFLE_FILE}" -d /ruffle && \
rm -f "${RUFFLE_FILE}";
ARG RUFFLE_SHA256=178870c5e7dd825a8df35920dfc5328d83e53f3c4d5d95f70b1ea9cd13494151
FROM alpine:${ALPINE_VERSION} AS nginx-build
RUN wget "https://github.com/ruffle-rs/ruffle/releases/download/${RUFFLE_VERSION}/${RUFFLE_FILE}" && \
echo "${RUFFLE_SHA256} ${RUFFLE_FILE}" | sha256sum -c - && \
unzip -o "${RUFFLE_FILE}" -d /ruffle && \
rm -f "${RUFFLE_FILE}"
# BUILD NGINX MODULE WITH MOD_ZIP
FROM alpine:${ALPINE_VERSION}@sha256:${ALPINE_SHA256} AS nginx-build
RUN apk add --no-cache \
gcc \
@@ -115,14 +137,14 @@ ARG NGINX_VERSION
# The specified commit SHA is the latest commit on the `master` branch at the time of writing.
# It includes a fix to correctly calculate CRC-32 checksums when using upstream subrequests.
# TODO: Move to a tagged release of `mod_zip`, once a version newer than 1.3.0 is released.
ARG NGINX_MOD_ZIP_SHA=a9f9afa441117831cc712a832c98408b3f0416f6
ARG NGINX_MOD_ZIP_COMMIT=a9f9afa441117831cc712a832c98408b3f0416f6
# Clone both nginx and `ngx_http_zip_module` repositories, needed to compile the module from source.
# Clone both `nginx` and `ngx_http_zip_module` repositories, needed to compile the module from source.
# This is needed to be able to dinamically load it as a module in the final image. `nginx` Docker
# images do not have a simple way to include third-party modules.
RUN git clone https://github.com/evanmiller/mod_zip.git && \
cd ./mod_zip && \
git checkout "${NGINX_MOD_ZIP_SHA}" && \
git checkout "${NGINX_MOD_ZIP_COMMIT}" && \
cd ../ && \
git clone --branch "release-${NGINX_VERSION}" --depth 1 https://github.com/nginx/nginx.git && \
cd ./nginx && \
@@ -131,10 +153,10 @@ RUN git clone https://github.com/evanmiller/mod_zip.git && \
chmod 644 ./objs/ngx_http_zip_module.so
FROM nginx:${NGINX_VERSION}-alpine${ALPINE_VERSION} AS production-stage
# PRODUCTION STAGE
FROM nginx:${NGINX_VERSION}-alpine${ALPINE_VERSION}@sha256:${NGINX_SHA256} AS production-stage
ARG WEBSERVER_FOLDER=/var/www/html
# Install required packages and dependencies
RUN apk add --no-cache \
bash \
libmagic \
@@ -175,13 +197,15 @@ COPY ./docker/gunicorn/logging.conf /etc/gunicorn/logging.conf
# User permissions
# - Create default user `romm` (1000) and group `romm` (1000).
# - Create base directories and make default user/group the owner.
# - Make nginx configuration files writable by everyone, for `envsubst` to work
# when a custom UID/GID is used.
# - Make nginx configuration files writable by everyone for `envsubst` to work
RUN addgroup -g 1000 -S romm && adduser -u 1000 -D -S -G romm romm && \
mkdir /romm /redis-data && chown romm:romm /romm /redis-data && \
mkdir /romm /redis-data && \
chown romm:romm /romm /redis-data && \
chmod 755 /romm /redis-data && \
chmod -R a+w /etc/nginx/conf.d
# SLIM IMAGE
FROM scratch AS slim-image
COPY --from=production-stage / /
@@ -190,10 +214,15 @@ COPY --from=backend-build /src/.venv /src/.venv
ENV PATH="/src/.venv/bin:${PATH}"
# Security: Set security-focused environment variables
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV PYTHONPATH=/backend
# Declare the supported volumes
VOLUME ["/romm/resources", "/romm/library", "/romm/assets", "/romm/config", "/redis-data"]
# Expose ports and start
# Expose non-privileged ports
EXPOSE 8080 6379/tcp
WORKDIR /romm
@@ -201,6 +230,7 @@ ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["/init"]
# FULL IMAGE
FROM slim-image AS full-image
ARG WEBSERVER_FOLDER=/var/www/html
COPY --from=emulator-stage /emulatorjs ${WEBSERVER_FOLDER}/assets/emulatorjs

View File

@@ -23,6 +23,7 @@
"qrcode": "^1.5.4",
"semver": "^7.6.2",
"socket.io-client": "^4.7.5",
"tailwindcss": "^4.0.0",
"vanilla-tilt": "^1.8.1",
"vue": "^3.4.27",
"vue-i18n": "^11.1.10",
@@ -44,7 +45,6 @@
"eslint-plugin-vue": "^9.33.0",
"globals": "^16.0.0",
"openapi-typescript-codegen": "^0.29.0",
"tailwindcss": "^4.0.0",
"typescript": "^5.7.3",
"typescript-eslint": "^8.42.0",
"vite": "^6.3.6",
@@ -8374,7 +8374,6 @@
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.12.tgz",
"integrity": "sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==",
"dev": true,
"license": "MIT"
},
"node_modules/tapable": {

View File

@@ -21,7 +21,7 @@
"type": "module",
"scripts": {
"dev": "vite --host",
"build": "npm run typecheck && vite build",
"build": "vite build",
"preview": "vite preview",
"typecheck": "vue-tsc --noEmit",
"generate": "openapi --input http://127.0.0.1:3000/openapi.json --output ./src/__generated__ --client axios --useOptions --useUnionTypes --exportServices false --exportSchemas false --exportCore false",
@@ -42,6 +42,7 @@
"qrcode": "^1.5.4",
"semver": "^7.6.2",
"socket.io-client": "^4.7.5",
"tailwindcss": "^4.0.0",
"vanilla-tilt": "^1.8.1",
"vue": "^3.4.27",
"vue-i18n": "^11.1.10",
@@ -63,7 +64,6 @@
"eslint-plugin-vue": "^9.33.0",
"globals": "^16.0.0",
"openapi-typescript-codegen": "^0.29.0",
"tailwindcss": "^4.0.0",
"typescript": "^5.7.3",
"typescript-eslint": "^8.42.0",
"vite": "^6.3.6",