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_HOSTpoints topassbolt-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.confshould proxy traffic only topassbolt-appon 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.”