Security Best Practices for Running the MCP-PostgreSQL-Ops Server in Production
Run the MCP-PostgreSQL-Ops server with a dedicated low-privilege database user, enforce TLS connections via sslmode=verify-full, enable Bearer-token authentication using the --auth-enable flag, and isolate the container network to ensure a hardened production deployment.
Deploying the MCP-PostgreSQL-Ops server in a production environment requires a defense-in-depth strategy that secures the database connection, the server process, and the surrounding infrastructure. According to the repository's source code, the server includes built-in safety mechanisms such as automatic password masking and non-root container execution, but production deployments must harden these defaults further. This guide covers ten critical security best practices derived directly from the SECURITY.md, Dockerfile.MCP-Server, and core Python modules.
1. Enforce the Principle of Least Privilege on the Database
The MCP server only needs to query system catalogs and statistics views. It does not require superuser privileges or write access to application data.
Create a Dedicated Read-Only Role
Create a role that has only CONNECT permission and SELECT rights on specific system catalogs used by the tools in src/mcp_postgresql_ops/functions.py. Grant pg_read_all_stats only if you need to query pg_stat_statements or pg_stat_monitor.
CREATE ROLE mcp_user LOGIN PASSWORD '••••••••';
GRANT CONNECT ON DATABASE ecommerce TO mcp_user;
GRANT USAGE ON SCHEMA public TO mcp_user;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO mcp_user;
GRANT SELECT ON pg_catalog.pg_stat_activity TO mcp_user;
GRANT SELECT ON pg_catalog.pg_settings TO mcp_user;
GRANT SELECT ON pg_catalog.pg_user TO mcp_user;
GRANT pg_read_all_stats TO mcp_user; -- Optional: needed for pg_stat_* extensions
Never grant SUPERUSER or CREATEDB attributes to this account. The Security Best Practices section in SECURITY.md explicitly recommends this isolation to prevent privilege escalation.
2. Secure Database Connections with TLS
Always encrypt traffic between the MCP server and PostgreSQL. Configure the server to verify the database identity and reject unencrypted connections.
- Set
ssl = oninpostgresql.confon the database server. - Use
sslmode=verify-fullin the connection string to ensure certificate validation. - Configure the client certificate authority to prevent man-in-the-middle attacks.
This ensures that credentials and query results cannot be intercepted on the network.
3. Protect Credentials Using Docker Secrets
Never commit secrets to version control. The repository provides a .env.example template that you must copy to a real .env file excluded from Git.
For production, migrate from environment variables to Docker secrets or an external secrets manager like HashiCorp Vault or AWS Secrets Manager.
services:
mcp-server:
image: call518/mcp-server-postgresql-ops:latest
env_file: .env
secrets:
- POSTGRES_PASSWORD
- REMOTE_SECRET_KEY
command: >
uvx --python 3.12 mcp-postgresql-ops
--type streamable-http
--host 0.0.0.0
--port 8000
--auth-enable
--secret-key $REMOTE_SECRET_KEY
secrets:
POSTGRES_PASSWORD:
file: ./secrets/postgres_password.txt
REMOTE_SECRET_KEY:
file: ./secrets/remote_secret_key.txt
This approach keeps sensitive material out of environment variables and process listings.
4. Enable Bearer-Token Authentication
The HTTP transport must never run unauthenticated in production. In src/mcp_postgresql_ops/mcp_main.py, the server supports the --auth-enable flag to enforce Bearer-token validation.
Configure your .env with a strong random key:
REMOTE_AUTH_ENABLE=true
REMOTE_SECRET_KEY=3b2c5f8e9a1d4f6b7c9e0a1b2c3d4e5f6g7h8i9j0k
Use at least 32 cryptographically random characters. The Security & Authentication section in README.md warns that running with REMOTE_AUTH_ENABLE=false exposes the database metadata to any network client.
5. Isolate the Container Network
The Dockerfile.MCP-Server already configures the container to run as a non-root appuser. Build upon this with network-level controls:
- Restrict inbound traffic to port
8000to trusted subnets only. - Deploy security groups or firewalls that block external access to the PostgreSQL port (5432).
- Allow only the MCP container or trusted bastion hosts to reach the database.
This layering ensures that even if the application layer is compromised, the attacker cannot pivot directly to the database host.
6. Implement HTTPS with a Reverse Proxy
Terminate TLS at a reverse proxy (Nginx or Traefik) placed in front of the MCP server. The proxy should:
- Handle TLS certificate management (Let's Encrypt or corporate PKI).
- Verify the
AuthorizationBearer token header. - Forward only validated requests to
localhost:8000inside the container network.
Never expose the plain HTTP port 8000 directly to the public internet.
7. Mask Sensitive Data in Logs
The repository includes a sanitize_connection_info function in src/mcp_postgresql_ops/functions.py that automatically redacts passwords from log output. Ensure this behavior is active by verifying that connection string logging is enabled and that the sanitization filter is not disabled.
Forward container logs to a centralized system (ELK, CloudWatch, or Splunk) and configure alerts for authentication failures or requests from unexpected IP addresses.
8. Keep Dependencies Updated
Security patches for PostgreSQL and Python dependencies must be applied promptly. The repository includes .github/dependabot.yml to automate dependency updates. Enable this to receive pull requests for security advisories affecting the server.
- Upgrade PostgreSQL to the latest supported major version (12–17).
- Rebuild the container image monthly to incorporate OS-level security patches.
9. Harden Container Images
The official images use the Percona PostgreSQL base as defined in Dockerfile.MCP-Server and Dockerfile.MCPO-Proxy. Before deployment:
- Scan images with Trivy, Docker Bench, or Grype to detect CVEs.
- Remove unnecessary packages and ensure the
appuserhas no sudo privileges. - Pin image digests rather than using floating tags like
latest.
10. Audit PostgreSQL Extensions
Extensions like pg_stat_statements and pg_stat_monitor expose detailed query fingerprints. Enable them only when required by specific tools, as detailed in the Extension-Dependent Tools matrix in README.md. When disabled, remove them from shared_preload_libraries to prevent information leakage via the statistics views.
Summary
- Database: Use a dedicated role with
pg_read_all_statsonly, never superuser. - Transport: Enforce
sslmode=verify-fulland terminate HTTPS at a reverse proxy. - Authentication: Enable
--auth-enablewith a strongREMOTE_SECRET_KEY. - Secrets: Store credentials in Docker secrets or a vault, never in Git.
- Runtime: Run as non-root
appuser, restrict port8000, and mask logs viasanitize_connection_info. - Maintenance: Enable Dependabot (
.github/dependabot.yml) and scan images before deployment.
Frequently Asked Questions
Does MCP-PostgreSQL-Ops require superuser access to PostgreSQL?
No. The server only queries system catalogs and statistics views. Create a dedicated role with CONNECT, USAGE, and SELECT permissions on pg_catalog tables. Grant pg_read_all_stats only if you need extension-dependent tools like pg_stat_statements. Superuser rights violate the principle of least privilege and are unnecessary for the tools implemented in src/mcp_postgresql_ops/functions.py.
How do I enable authentication for the MCP server HTTP API?
Pass the --auth-enable flag when starting the server, as implemented in src/mcp_postgresql_ops/mcp_main.py. Set REMOTE_AUTH_ENABLE=true and REMOTE_SECRET_KEY to a random 32-character string in your environment. Production deployments must not run with authentication disabled, as warned in SECURITY.md.
What is the safest way to manage database credentials in production?
Use Docker secrets or an external secrets manager rather than environment variables in your .env file. The repository provides a .env.example template that you should copy and exclude from version control. Reference the Docker Deployment Security subsection in SECURITY.md for the Docker Compose syntax using the secrets: top-level element.
How does the server protect passwords in log files?
The sanitize_connection_info function in src/mcp_postgresql_ops/functions.py automatically parses connection strings and replaces password values with [REDACTED] before logging. This prevents accidental credential exposure in container logs or centralized logging systems like ELK or CloudWatch.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →