Building a Segmented, Secure Multi-Container Application with Podman

By DeadSwitch | The Cyber Ghost
“In silence, we rise. In the switch, we fade.”


Modern web applications are never just one service.
They’re a fortress of moving parts – and every connection is a potential attack surface.
If you’re still putting the entire stack into one fat container…
You’re building your future breach.

Today, DeadSwitch shows you how to model a real-world application using Podman the right way:
✅ Segmented containers
✅ Minimal exposure
✅ Controlled communication
✅ Hardened by default

We’ll use Passbolt (a self-hosted password manager) as our target app, fronted by NGINX SSL reverse proxy, backed by MariaDB – but only the proxy should be visible to the outside world.

Let’s dive.


1. Design First: The Segmentation Plan

Architecture Overview:

[ Client ] → HTTPS → [ NGINX Reverse Proxy ] → [ Passbolt App ] → [ MariaDB Database ]

  • Only NGINX faces the internet.
  • Passbolt listens only internally.
  • MariaDB listens only internally.
  • All components sit on a private Podman network.

Rule of thumb:
🔒 If it doesn’t need to be exposed, it must not be exposed.


2. Create a Private Podman Network

First, set up an isolated network for the app:

podman network create passbolt_net
  • Internal traffic only
  • Automatic DNS between containers

3. Deploy MariaDB (Database Layer)

Spin up MariaDB safely:

podman run -d \
  --name passbolt-db \
  --network passbolt_net \
  -e MYSQL_ROOT_PASSWORD=StrongRootPass123 \
  -e MYSQL_DATABASE=passboltdb \
  -e MYSQL_USER=passboltuser \
  -e MYSQL_PASSWORD=StrongUserPass123 \
  --health-cmd='mysqladmin ping --silent' \
  --health-interval=30s \
  --health-timeout=5s \
  --health-retries=3 \
  docker.io/mariadb:latest

Key points:

  • Use environment variables for credentials (then rotate/change them later).
  • Enable basic health checks.
  • Network is internal only.

4. Deploy Passbolt (Application Layer)

Now the app:

podman run -d \
  --name passbolt-app \
  --network passbolt_net \
  -e DATASOURCES_DEFAULT_HOST=passbolt-db \
  -e DATASOURCES_DEFAULT_USERNAME=passboltuser \
  -e DATASOURCES_DEFAULT_PASSWORD=StrongUserPass123 \
  -e DATASOURCES_DEFAULT_DATABASE=passboltdb \
  -e APP_FULL_BASE_URL=https://yourdomain.com \
  docker.io/passbolt/passbolt:latest

Note:

  • DATASOURCES_DEFAULT_HOST points to passbolt-db – Podman DNS resolves container names automatically.
  • Passbolt expects SSL/TLS – even if it’s terminated by NGINX, Passbolt needs the correct external URL.

5. Deploy NGINX (Reverse Proxy Layer)

Finally, shield everything behind NGINX:

podman run -d \
  --name passbolt-proxy \
  --network passbolt_net \
  -p 443:443 \
  -v /path/to/nginx.conf:/etc/nginx/nginx.conf:ro \
  -v /path/to/certs/:/etc/nginx/certs/:ro \
  docker.io/nginx:stable

Things to prepare:

  • Your nginx.conf should proxy traffic only to passbolt-app on internal network.
  • SSL certs are stored in /path/to/certs/.
  • Use strong ciphers, HTTP/2, and security headers.

Example NGINX snippet:

server {
    listen 443 ssl http2;
    server_name yourdomain.com;

    ssl_certificate /etc/nginx/certs/fullchain.pem;
    ssl_certificate_key /etc/nginx/certs/privkey.pem;

    location / {
        proxy_pass http://passbolt-app:80;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Security Tips for the Full Stack

  • No direct database exposure:
    Only the Passbolt app talks to MariaDB.
  • Limit root:
    Prefer containers running as non-root users where possible.
  • Resource control:
    Add memory, CPU, and process limits to each container.
  • Logs:
    Mount logs to host directories carefully (read-only where possible).
  • Regularly rotate passwords and certs.
  • Firewall external ports:
    Only port 443 should be publicly reachable.

Why Bother With All This?

Defense in depth: Every layer forces an attacker to work harder.
Compartmentalization: A compromise in one container doesn’t doom the others.
Operational clarity: Clear separation of concerns between services.
Minimal exposure: Less attack surface = fewer breach vectors.

You don’t protect systems by accident.
You model them to assume breach, and you isolate every move.


TL;DR

🔹 Use Podman (rootless, daemonless, hardened).
🔹 Segment your containers by function and exposure level.
🔹 Connect them via internal-only networks.
🔹 Expose only what must be public (and nothing more).

Real security doesn’t happen at deployment.
It’s designed at inception.

DeadSwitch | The Cyber Ghost
“Fear the silence. Fear the switch.”

Leave a comment