Self-Hosting Romm: Your Retro Game Library with Docker and HTTPS
Romm is a fantastic web-based application for managing and playing your retro game library. While running it locally is great, self-hosting it on a server allows access from anywhere. This guide will walk you through setting up Romm using Docker and Docker Compose, complete with MariaDB for the database and Nginx as a reverse proxy to enable secure HTTPS access, even if you only have an IP address for your server.
Prerequisites
- A server (VPS or home server) running Linux (Ubuntu is used in examples).
- Docker and Docker Compose installed.
- Basic familiarity with the Linux terminal.
- An IP address (IPv4 or IPv6) for your server accessible from the internet.
- Ports 80 and 443 open on your server’s firewall (if applicable).
Step 1: Initial Docker Compose Setup
First, we need a docker-compose.yml file to define our services: romm itself and its database romm-db (MariaDB).
Create a file named docker-compose.yml:
version: "3" # Note: Version tag is optional in newer Docker Compose
volumes:
mysql_data:
romm_resources:
romm_redis_data:
services:
romm:
image: rommapp/romm:latest
container_name: romm
restart: unless-stopped
environment:
- DB_HOST=romm-db
- DB_NAME=romm # Should match MARIADB_DATABASE in mariadb
- DB_USER=romm-user # Should match MARIADB_USER in mariadb
# IMPORTANT: Replace with a strong, unique password!
- DB_PASSWD=your_secure_db_password
# IMPORTANT: Generate with `openssl rand -hex 32` and keep secret!
- ROMM_AUTH_SECRET_KEY=your_generated_secret_key
# Optional: Add API keys for enhanced metadata fetching
- IGDB_CLIENT_ID=
- IGDB_CLIENT_SECRET=
- MOBYGAMES_API_KEY=
- STEAMGRIDDB_API_KEY=
volumes:
- romm_resources:/romm/resources
- romm_redis_data:/redis-data
# Map your host library directory to the container
- ./data/library:/romm/library
- ./data/assets:/romm/assets
- ./data/config:/romm/config
depends_on:
romm-db:
condition: service_healthy
romm-db:
image: mariadb:latest
container_name: romm-db
restart: unless-stopped
environment:
# IMPORTANT: Use the same secure password as DB_PASSWD above!
- MARIADB_ROOT_PASSWORD=your_secure_db_password
- MARIADB_DATABASE=romm
- MARIADB_USER=romm-user
- MARIADB_PASSWORD=your_secure_db_password
volumes:
- mysql_data:/var/lib/mysql
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 5
Before proceeding:
- Replace
your_secure_db_passwordwith a strong, unique password in both therommandromm-dbsections. - Generate a secret key using
openssl rand -hex 32in your terminal and replaceyour_generated_secret_key. - Create the host directories for your library, assets, and config:
mkdir -p data/library data/assets data/config. - (Optional) Add your API keys if you have them.
At this point, you could run docker compose up -d and access Romm via http://<your_server_ip>:8080 (if you temporarily added a ports: - 8080:8080 section to the romm service). But our goal is secure HTTPS on the standard port 443.
Step 2: Introducing Nginx for HTTPS
To handle HTTPS, we’ll use Nginx as a reverse proxy. Nginx will listen on ports 80 (HTTP) and 443 (HTTPS). It will redirect HTTP traffic to HTTPS and handle the SSL/TLS encryption for HTTPS traffic, forwarding the decrypted requests to the romm container on its internal port 8080.
Step 3: Updating docker-compose.yml
Modify your docker-compose.yml to add the Nginx service and remove the direct port mapping from the romm service (if you added one).
version: "3"
volumes:
mysql_data:
romm_resources:
romm_redis_data:
services:
romm:
image: rommapp/romm:latest
container_name: romm
restart: unless-stopped
environment:
- DB_HOST=romm-db
- DB_NAME=romm
- DB_USER=romm-user
- DB_PASSWD=your_secure_db_password
- ROMM_AUTH_SECRET_KEY=your_generated_secret_key
- IGDB_CLIENT_ID=
- IGDB_CLIENT_SECRET=
- MOBYGAMES_API_KEY=
- STEAMGRIDDB_API_KEY=
volumes:
- romm_resources:/romm/resources
- romm_redis_data:/redis-data
- ./data/library:/romm/library
- ./data/assets:/romm/assets
- ./data/config:/romm/config
# No 'ports' section here anymore; Nginx handles external access
depends_on:
romm-db:
condition: service_healthy
romm-db:
# ... (romm-db configuration remains the same) ...
image: mariadb:latest
container_name: romm-db
restart: unless-stopped
environment:
- MARIADB_ROOT_PASSWORD=your_secure_db_password
- MARIADB_DATABASE=romm
- MARIADB_USER=romm-user
- MARIADB_PASSWORD=your_secure_db_password
volumes:
- mysql_data:/var/lib/mysql
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 5
nginx:
image: nginx:latest
container_name: romm-nginx
restart: unless-stopped
ports:
# Map host ports 80 and 443 to the container
- "80:80"
- "443:443"
volumes:
# Mount Nginx configuration
- ./nginx/conf.d:/etc/nginx/conf.d:ro
# Mount SSL certificates
- ./nginx/certs:/etc/nginx/certs:ro
depends_on:
- romm
Step 4: Creating the Nginx Configuration
Now, create the directories for Nginx configuration and certificates:
mkdir -p nginx/conf.d nginx/certs
Create a file named nginx/conf.d/default.conf with the following content. This configuration listens on ports 80 and 443, redirects HTTP to HTTPS, and proxies requests to the romm service.
server {
# Explicitly listen on IPv6 and IPv4 for HTTP
listen [::]:80 ipv6only=on;
listen 80;
# Replace with your server's IP address or domain name
server_name your_server_ip_or_domain;
# Redirect HTTP to HTTPS
location / {
return 301 https://$host$request_uri;
}
}
server {
# Explicitly listen on IPv6 and IPv4 for HTTPS
listen [::]:443 ssl ipv6only=on;
listen 443 ssl;
# Replace with your server's IP address or domain name
server_name your_server_ip_or_domain;
# --- SSL Certificate Configuration ---
# Option 1: Let's Encrypt (Requires a domain name)
# ssl_certificate /etc/nginx/certs/live/your_domain.com/fullchain.pem;
# ssl_certificate_key /etc/nginx/certs/live/your_domain.com/privkey.pem;
# Option 2: Self-Signed Certificates (Use if you only have an IP address)
ssl_certificate /etc/nginx/certs/nginx-selfsigned.crt;
ssl_certificate_key /etc/nginx/certs/nginx-selfsigned.key;
# --- End SSL Certificate Configuration ---
# Recommended SSL settings (adjust ciphers as needed)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
# ssl_stapling and ssl_stapling_verify are commented out
# as they are not applicable to self-signed certs.
# ssl_stapling on;
# ssl_stapling_verify on;
location / {
proxy_pass http://romm:8080; # Forward requests to the romm service
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Support for WebSockets (if needed by romm)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Important:
- Replace
your_server_ip_or_domainwith your actual server’s public IP address (e.g.,[2a01:4f8:c013:f709::1]for IPv6,192.0.2.1for IPv4) or your domain name if you have one. Remember to enclose IPv6 addresses in square brackets ([]). - Choose one of the SSL certificate options (Let’s Encrypt or Self-Signed) and comment out or delete the lines for the other option.
Step 5: Handling HTTPS without a Domain Name (Self-Signed Certs)
If you don’t have a domain name and are using your server’s IP address, you generally cannot get publicly trusted certificates from authorities like Let’s Encrypt. The solution is to generate your own “self-signed” certificate.
-
Ensure Nginx Config Uses Self-Signed Paths: Make sure the
ssl_certificateandssl_certificate_keydirectives innginx/conf.d/default.confpoint to/etc/nginx/certs/nginx-selfsigned.crtand/etc/nginx/certs/nginx-selfsigned.key. -
Generate the Certificate: Run the following
opensslcommand in your terminal, replacingyour_server_ipwith your actual IP address (use brackets for IPv6):# Example for IPv6 openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout nginx/certs/nginx-selfsigned.key \ -out nginx/certs/nginx-selfsigned.crt \ -subj "/CN=[2a01:4f8:c013:f709::1]" # Example for IPv4 # openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ # -keyout nginx/certs/nginx-selfsigned.key \ # -out nginx/certs/nginx-selfsigned.crt \ # -subj "/CN=192.0.2.1"This creates the certificate (
.crt) and private key (.key) files in thenginx/certs/directory, valid for 365 days.
Step 6: Running the Setup
With the docker-compose.yml and Nginx configuration ready (and self-signed certificates generated if needed), you can start the services:
docker compose up -d
This command will download the necessary images (if not already present), create the containers, networks, and volumes, and start everything in the background.
Check the status:
docker compose ps
You should see romm, romm-db, and romm-nginx running.
Check Nginx logs for errors (especially useful during troubleshooting):
docker logs romm-nginx
Step 7: Accessing Romm via HTTPS
Open your web browser and navigate to:
https://your_server_ip_or_domain
(Replace your_server_ip_or_domain with the same IP or domain used in the Nginx config).
Important Note on Self-Signed Certificates: If you used self-signed certificates, your browser will display a prominent security warning (e.g., “Your connection is not private,” “Warning: Potential Security Risk Ahead”). This is expected because the certificate isn’t signed by a trusted authority known to the browser. You will need to manually accept the risk (usually under an “Advanced” or “Details” section) to proceed. The connection is encrypted, but the identity verification step fails.
Troubleshooting Tips
ERR_CONNECTION_REFUSEDor Timeout:- Check if
docker compose psshows all containers running, especiallyromm-nginx. - Verify
docker psshows port 80 and 443 mapped correctly for theromm-nginxcontainer. - Check your server’s firewall (
sudo ufw status verboseon Ubuntu). Ensure ports 80 and 443 are allowed for incoming TCP traffic (e.g.,sudo ufw allow 80/tcp,sudo ufw allow 443/tcp). - Check external firewalls (cloud provider security groups, hosting provider firewall). Make sure they allow TCP traffic on ports 80 and 443 to your server’s IP.
- Check if
ERR_ADDRESS_UNREACHABLE:- Often caused by routing issues or firewalls blocking the path to your server.
- Check external firewalls (see above).
- Test basic connectivity: Ping your server’s IP from your local machine (
ping your_server_iporping -6 your_ipv6_address). - Check client-side issues: VPNs (especially corporate ones) can interfere with routing or block specific ports. Try accessing the server with the VPN disconnected.
- Nginx Errors (Check
docker logs romm-nginx):- Syntax errors in
nginx/conf.d/default.conf. - Certificate file not found errors (ensure paths in the config match the generated/placed files and the volume mounts in
docker-compose.ymlare correct). - Permission errors (less common with Docker mounts, but check file permissions if issues persist).
- Syntax errors in
- 502 Bad Gateway: Nginx is running but cannot connect to the upstream
rommservice.- Check if the
rommcontainer is running (docker compose ps). - Check
rommcontainer logs (docker logs romm) for startup errors.
- Check if the
Conclusion
You now have a self-hosted Romm instance running securely over HTTPS! While using self-signed certificates for IP-based access works, getting a domain name and using Let’s Encrypt certificates is recommended for a smoother experience without browser warnings. Enjoy managing and playing your retro game collection!