• Skip to secondary menu
  • Skip to main content
  • Skip to primary sidebar
  • Home
  • Projects
  • Products
  • Themes
  • Tools
  • Request for Quote

Vengala Vinay

Having 12+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » Setting up isolated local testing environments using Docker Compose, DNSMasq, and self-signed local SSL on openSUSE

Setting up isolated local testing environments using Docker Compose, DNSMasq, and self-signed local SSL on openSUSE

Prerequisites and System Setup

This guide assumes a working openSUSE Leap or Tumbleweed installation. We’ll be leveraging Docker Compose for service orchestration, DNSMasq for local DNS resolution, and Nginx for SSL termination with self-signed certificates. Ensure you have the following packages installed. If not, use zypper to install them:

  • Docker and Docker Compose: docker-compose (or docker compose for newer versions)
  • DNSMasq: dnsmasq
  • Nginx: nginx

Verify your Docker installation:

  • docker --version
  • docker compose version (or docker-compose --version)

Verify DNSMasq installation:

  • dnsmasq --version

Verify Nginx installation:

  • nginx -v

Configuring DNSMasq for Local Domain Resolution

We need DNSMasq to resolve custom local domains (e.g., app1.local, db.local) to 127.0.0.1. This allows our applications within Docker containers to refer to each other using hostnames, and for our local machine to access them directly.

First, create a configuration file for DNSMasq. It’s good practice to keep custom configurations separate from the main dnsmasq.conf. We’ll use /etc/dnsmasq.d/local.conf.

  • Create the directory if it doesn’t exist: sudo mkdir -p /etc/dnsmasq.d/
  • Create and edit the configuration file: sudo nano /etc/dnsmasq.d/local.conf

Add the following lines to /etc/dnsmasq.d/local.conf:

# Listen only on the loopback interface
listen=127.0.0.1

# Specify a domain for local resolution
local=/local/

# Resolve all .local domains to 127.0.0.1
address=/local/127.0.0.1

# Cache DNS queries
cache-size=1000

# Log DNS queries (optional, for debugging)
log-queries
log-dhcp

Next, we need to configure our system to use DNSMasq for DNS resolution. Edit /etc/resolv.conf. It’s crucial to note that on many modern Linux systems, /etc/resolv.conf is managed by NetworkManager or systemd-resolved. If it’s a symlink, you’ll need to configure the managing service. For a direct setup or if /etc/resolv.conf is not a symlink:

Backup the original file:

  • sudo cp /etc/resolv.conf /etc/resolv.conf.backup

Edit the file:

  • sudo nano /etc/resolv.conf

Ensure the first nameserver points to 127.0.0.1:

nameserver 127.0.0.1
options edns0 trust-ad

If /etc/resolv.conf is managed by NetworkManager, you’ll need to configure NetworkManager to use DNSMasq. Edit /etc/NetworkManager/NetworkManager.conf and ensure the dns setting is set to dnsmasq:

[main]
plugins=ifcfg-suse
dns=dnsmasq

After making changes to NetworkManager, restart the service:

  • sudo systemctl restart NetworkManager

Finally, restart DNSMasq to apply the new configuration:

  • sudo systemctl enable dnsmasq
  • sudo systemctl restart dnsmasq

Test your DNS resolution. You should be able to ping a local domain:

  • ping app1.local

This should resolve to 127.0.0.1 and show successful pings.

Generating Self-Signed SSL Certificates

For secure local communication, we’ll generate self-signed SSL certificates. This is essential for testing HTTPS endpoints. We’ll create a certificate for a wildcard domain (e.g., *.local) to cover all our local subdomains.

First, create a directory to store your certificates:

  • sudo mkdir -p /etc/nginx/ssl/local
  • cd /etc/nginx/ssl/local

Generate the private key and the certificate signing request (CSR) using OpenSSL. We’ll create a configuration file for OpenSSL to specify the Subject Alternative Names (SANs), which is crucial for modern browsers.

Create an OpenSSL configuration file, e.g., /etc/nginx/ssl/local/openssl.cnf:

[req]
distinguished_name = req_distinguished_name
req_extensions = req_extensions
prompt = no

[req_distinguished_name]
C = US
ST = California
L = San Francisco
O = MyCompany
OU = IT
CN = *.local

[req_extensions]
basicConstraints = CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names

[alt_names]
DNS.1 = *.local
DNS.2 = localhost

Now, generate the private key and the self-signed certificate:

# Generate private key
sudo openssl genrsa -out local.key 2048

# Generate CSR and self-signed certificate
# The -days parameter sets the validity period (e.g., 3650 days for 10 years)
sudo openssl req -x509 -new -nodes -key local.key -sha256 -days 3650 -out local.crt -config openssl.cnf

You should now have local.key (private key) and local.crt (certificate) in /etc/nginx/ssl/local/. Secure the private key:

  • sudo chmod 600 /etc/nginx/ssl/local/local.key

To trust this certificate on your local machine (and avoid browser warnings), you need to import it into your system’s trust store. The exact steps vary by OS and browser, but generally involve:

  • Linux (System-wide): Copy the certificate to /etc/pki/trust-source/anchors/ and run sudo update-ca-certificates.
  • Firefox: Go to Settings -> Privacy & Security -> Certificates -> View Certificates -> Authorities -> Import.
  • Chrome/Chromium: Use the system trust store.

For openSUSE, you can import it system-wide:

  • sudo cp /etc/nginx/ssl/local/local.crt /etc/pki/trust-source/anchors/
  • sudo update-ca-certificates

Setting up Nginx as a Local Reverse Proxy

Nginx will act as our reverse proxy, handling SSL termination and routing requests to the appropriate Docker containers. We’ll configure Nginx to listen on ports 80 and 443, and use our self-signed certificate.

Create a new Nginx configuration file for our local environments, e.g., /etc/nginx/conf.d/local-proxy.conf.

  • sudo nano /etc/nginx/conf.d/local-proxy.conf

Add the following configuration. This is a template; you’ll add specific server blocks for each application.

# Redirect HTTP to HTTPS
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _; # Catch all for HTTP
    return 301 https://$host$request_uri;
}

# HTTPS server block for local domains
server {
    listen 443 ssl http2 default_server;
    listen [::]:443 ssl http2 default_server;
    server_name _; # Catch all for HTTPS

    ssl_certificate /etc/nginx/ssl/local/local.crt;
    ssl_certificate_key /etc/nginx/ssl/local/local.key;

    # Recommended SSL settings for security
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    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_timeout 10m;
    ssl_session_tickets off;
    ssl_stapling off; # OCSP stapling is not relevant for self-signed certs

    # Default fallback if no specific server_name matches
    location / {
        return 404 "Not Found: No specific server block configured for this host.";
    }

    # Add specific server blocks below for your applications
    # Example for app1.local:
    # include /etc/nginx/conf.d/app1.local.conf;
}

Test your Nginx configuration:

  • sudo nginx -t

If the test is successful, reload Nginx:

  • sudo systemctl reload nginx

Docker Compose for Isolated Environments

Now, let’s define our isolated testing environments using Docker Compose. Each environment can have its own set of services (web servers, databases, APIs, etc.). We’ll create a docker-compose.yml file for each distinct application or service group.

Consider an example scenario: a web application (app1) that depends on a database (db1). We’ll create a docker-compose.yml file for this.

Create a directory for your application’s Docker Compose setup, e.g., ~/dev/app1.

  • mkdir -p ~/dev/app1
  • cd ~/dev/app1

Create the docker-compose.yml file:

version: '3.8'

services:
  app1:
    build: . # Assumes a Dockerfile in the current directory
    container_name: app1_web
    ports:
      - "8000:80" # Map container port 80 to host port 8000 for direct access if needed
    environment:
      DATABASE_HOST: db1
      DATABASE_USER: appuser
      DATABASE_PASSWORD: securepassword
      DATABASE_NAME: appdb
    depends_on:
      - db1
    networks:
      - app1_net

  db1:
    image: postgres:14-alpine
    container_name: app1_db
    environment:
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD: securepassword
      POSTGRES_DB: appdb
    volumes:
      - db1_data:/var/lib/postgresql/data
    networks:
      - app1_net

networks:
  app1_net:
    driver: bridge

volumes:
  db1_data:

In this docker-compose.yml:

  • app1 service: This is your application. It’s configured to build from a local Dockerfile. It depends on db1 and uses environment variables to connect to it. We map port 8000 on the host to port 80 in the container.
  • db1 service: A PostgreSQL database instance. It uses environment variables for initial setup and a named volume db1_data for persistent storage.
  • app1_net: A custom bridge network for this environment. This ensures that services within this compose file can communicate using their service names (e.g., db1) and are isolated from other Docker networks by default.

You’ll also need a Dockerfile in the same directory (~/dev/app1/) for your app1 service. Here’s a basic example for a PHP application:

FROM php:8.2-apache

WORKDIR /var/www/html

COPY . /var/www/html

# Install any necessary PHP extensions or system packages
# RUN docker-php-ext-install pdo pdo_pgsql
# RUN apt-get update && apt-get install -y --no-install-recommends some-package && rm -rf /var/lib/apt/lists/*

# Ensure Apache can serve files
RUN chown -R www-data:www-data /var/www/html

Integrating Docker Compose with Nginx and DNSMasq

The final step is to configure Nginx to proxy requests for app1.local to the Docker container. We’ll create a separate Nginx configuration file for each application.

Create a new Nginx configuration file for app1.local, e.g., /etc/nginx/conf.d/app1.local.conf:

  • sudo nano /etc/nginx/conf.d/app1.local.conf
server {
    listen 443 ssl http2;
    server_name app1.local;

    ssl_certificate /etc/nginx/ssl/local/local.crt;
    ssl_certificate_key /etc/nginx/ssl/local/local.key;

    location / {
        proxy_pass http://127.0.0.1:8000; # Proxy to the host port mapped by Docker Compose
        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;
    }

    # Optional: If your app serves static files directly, configure that here
    # location /static/ {
    #     alias /path/to/your/static/files/;
    # }
}

In this Nginx configuration:

  • server_name app1.local;: This directive tells Nginx to use this block for requests to app1.local.
  • proxy_pass http://127.0.0.1:8000;: This forwards the request to the host port (8000) that we mapped in our docker-compose.yml. Nginx doesn’t need to know about Docker’s internal networking directly; it just talks to the host port.
  • The proxy_set_header directives are crucial for passing client information to the backend application.

After creating this file, test Nginx again and reload it:

  • sudo nginx -t
  • sudo systemctl reload nginx

Now, you can start your Docker Compose environment:

  • Navigate to your application’s directory: cd ~/dev/app1
  • Start the services: docker compose up -d (the -d flag runs containers in detached mode)

You should now be able to access your application securely via https://app1.local in your browser. The browser might show a warning initially if you haven’t fully trusted the self-signed certificate system-wide, but it should be accessible.

Managing Multiple Isolated Environments

To manage multiple isolated environments, simply repeat the process:

  • Create a new directory for each environment (e.g., ~/dev/app2).
  • Create a unique docker-compose.yml file for each environment, ensuring distinct network names and service names.
  • Create a corresponding Nginx configuration file (e.g., /etc/nginx/conf.d/app2.local.conf) that proxies to the host port exposed by that environment’s Docker Compose setup.
  • Ensure each application’s server_name in Nginx is unique (e.g., app2.local).
  • Start each environment using docker compose up -d in its respective directory.

For example, for a second application app2.local:

  • Create ~/dev/app2/docker-compose.yml and ~/dev/app2/Dockerfile.
  • Start it with cd ~/dev/app2 && docker compose up -d.
  • Create /etc/nginx/conf.d/app2.local.conf with server_name app2.local; and proxy_pass http://127.0.0.1:8001; (assuming port 8001 is mapped in app2’s compose file).
  • Reload Nginx: sudo systemctl reload nginx.

This setup provides a robust, isolated, and secure local development and testing environment, mimicking production setups with custom domains and SSL, all managed efficiently on your openSUSE workstation.

Reader Interactions

Leave a Reply Cancel reply

You must be logged in to post a comment.

Primary Sidebar

A little about the Author

Having 12+ Years of Experience in Software Development, Vinay is a principal software architect, senior systems engineer, and elite technical consultant. He specializes in bespoke PHP/WordPress development, high-performance Magento 2 & Shopify architectures, custom plugin/theme development from scratch, and legacy code modernization (including VB6, VB.NET, PyQt, and Crystal Reports). Known for solving complex database bottlenecks, speed optimization (Core Web Vitals), and advanced security code auditing, Vinay engineers production-ready systems designed to scale under heavy concurrent load conditions.



Chat on WhatsApp

Recent Posts

  • Debugging Guide: Diagnosing PHP-FPM child process pool exhaustion in multi-site network environments with modern tools
  • Debugging and Resolving complex namespace class loading collisions issues during heavy concurrent database traffic
  • Step-by-Step Guide: Offloading high-frequency customer support tickets metadata writes to a Redis KV store
  • How to refactor legacy event ticket registers queries using modern WP_Query and custom Transient caching
  • Step-by-Step Guide: Offloading high-frequency member profile directories metadata writes to a Redis KV store

Categories

  • apache (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (662)
  • Desktop Applications (14)
  • DevOps (7)
  • DevOps & Cloud Scaling (962)
  • Django (1)
  • Laravel (4)
  • Migration & Architecture (192)
  • Mobile Applications (24)
  • MySQL (1)
  • Performance & Optimization (873)
  • PHP (5)
  • PHP Development (49)
  • Plugins & Themes (244)
  • Programming Languages (9)
  • Python (20)
  • Ruby on Rails (1)
  • Security & Compliance (647)
  • SEO & Growth (492)
  • Server (118)
  • Ubuntu (9)
  • VB6 & VB.NET (8)
  • Web Applications & Frontend (19)
  • Web Assembly (Wasm) (2)
  • WordPress (22)
  • WordPress Plugin Development (726)
  • WordPress Theme Development (357)

Recent Posts

  • Debugging Guide: Diagnosing PHP-FPM child process pool exhaustion in multi-site network environments with modern tools
  • Debugging and Resolving complex namespace class loading collisions issues during heavy concurrent database traffic
  • Step-by-Step Guide: Offloading high-frequency customer support tickets metadata writes to a Redis KV store

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (873)
  • WordPress Plugin Development (726)
  • Debugging & Troubleshooting (662)
  • Security & Compliance (647)
  • SEO & Growth (492)

Our Products

  • ERP & LMS Systems (4)
  • Directories & Marketplaces (4)
  • Healthcare Portals (3)
  • Point of Sale (POS) (2)
  • E-Commerce Engines (2)

Our Services

  • E-Commerce Development (10)
  • WordPress Development (8)
  • Python & Desktop GUI (7)
  • General Consulting (7)
  • Legacy Modernization (5)
  • Mobile App Development (4)

Copyright © 2026 · Vinay Vengala