{"id":107543,"date":"2025-08-13T11:00:00","date_gmt":"2025-08-13T11:00:00","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=107543"},"modified":"2025-08-06T09:31:19","modified_gmt":"2025-08-06T09:31:19","slug":"running-postgresql-in-docker-with-proper-ssl-and-configuration","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/postgresql\/running-postgresql-in-docker-with-proper-ssl-and-configuration\/","title":{"rendered":"Secure PostgreSQL in Docker: SSL, Certificates &amp; Config Best Practices"},"content":{"rendered":"\n<p><a href=\"https:\/\/www.postgresql.org\/docs\/current\/intro-whatis.html\" target=\"_blank\" rel=\"noreferrer noopener\">PostgreSQL<\/a> is an open-source relational database management system valued for its power, standards-compliance, and extensibility.\u00a0 Whether you&#8217;re building a microservice, spinning up test environments, or maintaining a dev copy of production, it\u2019s often easier and cleaner to run Postgres in a container than to install it locally.<\/p>\n\n\n\n<p>Running Postgres in <a href=\"https:\/\/www.docker.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">Docker<\/a> is great for a quick test but what if you want it to behave like a proper, production-style setup with<a href=\"https:\/\/www.cloudflare.com\/learning\/ssl\/what-is-ssl\/\" target=\"_blank\" rel=\"noreferrer noopener\"> SSL<\/a> encryption, certificate-based authentication, persistent volumes, and custom configurations? In this article, we&#8217;ll find out how, tackling the various tasks involved such as:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Generating\u00a0and using self-signed SSL certificates with Postgres.<br><\/li>\n\n\n\n<li>Setting up a PostgreSQL Docker container that uses those certs for encrypted client connections.<br><\/li>\n\n\n\n<li>Configuring authentication for both automated services and human users.<br><\/li>\n\n\n\n<li>Controling the behavior of your Postgres instance using mounted config files.<\/li>\n<\/ul>\n<\/div>\n\n\n<p>We\u2019ll do all of this with security-conscious file ownership and permission control. By the end, you\u2019ll have a containerized PostgreSQL instance that behaves more like a hardened server than a throwaway development toy.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-why-secure-your-dockerized-postgresql\">Why Secure Your Dockerized PostgreSQL?<\/h2>\n\n\n\n<p>If you&#8217;re just experimenting, the default Docker Postgres image is fine. But when you\u2019re developing an application that will later use a properly secured production database, it&#8217;s worth aligning your local environment now, because:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>SSL support means traffic is encrypted, even locally.<br><\/li>\n\n\n\n<li>Certificate-based auth avoids relying on plaintext passwords or ENV variables.<br><\/li>\n\n\n\n<li>Custom config files give you control over connection rules, logging, and behavior.<br><\/li>\n\n\n\n<li>Mounted volumes let you persist data between container runs and simplify backups.<\/li>\n<\/ul>\n<\/div>\n\n\n<p>It also makes your setup portable \u2014 anyone else on your team can run the same containerized environment and be sure it behaves the same way.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-prerequisites-for-a-secure-setup\">Prerequisites for a Secure Setup<\/h2>\n\n\n\n<p>To get started, you&#8217;ll need:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/www.postgresql.org\/download\/linux\/ubuntu\/\" target=\"_blank\" rel=\"noreferrer noopener\">Docker (tested with engine 28.2<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/www.postgresql.org\/download\/\" target=\"_blank\" rel=\"noreferrer noopener\">Postgres client 17<\/a><\/li>\n\n\n\n<li>The latest version of <a href=\"https:\/\/openssl-library.org\/source\/\" target=\"_blank\" rel=\"noreferrer noopener\">OpenSSL, 3.5.0 LTS.<\/a><\/li>\n\n\n\n<li>A Unix-based OS (e.g., <a href=\"https:\/\/ubuntu.com\/download\" target=\"_blank\" rel=\"noreferrer noopener\">Ubuntu<\/a> 24.04)<\/li>\n\n\n\n<li>Basic knowledge of shell commands and file permissions<\/li>\n<\/ul>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\">Step 1: Set Up SSL\/TLS for Encrypted Connections<\/h2>\n\n\n\n<p>PostgreSQL supports SSL, but it doesn\u2019t come enabled in the default Docker image. You\u2019ll need to create the necessary certificate and key files first \u2013 it\u2019s not difficult, just a few <strong><a href=\"https:\/\/www.openssl.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">OpenSSL<\/a><\/strong> commands \u2013 and then configure the container to use them.<\/p>\n\n\n\n<p>We\u2019ll start by generating self-signed certificates using OpenSSL. Later, you could replace them with ones from a trusted certificate authority like <a href=\"https:\/\/letsencrypt.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">Let&#8217;s Encrypt<\/a> if you plan to expose your database to the outside world.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-create-your-certificate-authority-ca\">Create Your Certificate Authority (CA)<\/h3>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >mkdir certs &amp;&amp; cd certs\n\n# Create CA private key\nopenssl genrsa -out ca-key.pem 4096\n<\/pre><\/div>\n\n\n\n<p>This creates an RSA private key via the OpenSSL command-line tool. It sets the key length to 4096 and saves the resulting private key in ca-key.pem.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Create Server Certificate<\/h3>\n\n\n\n<p>This is what the PostgreSQL container will present to clients when they connect:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" ># Certificate signing request\nopenssl req -new -key server-key.pem -out server-req.pem \\\n  -subj \"\/C=US\/ST=State\/L=City\/O=MyOrg\/OU=Dev\/CN=postgres\"\n\n# Sign the request with your CA\nopenssl x509 -req -in server-req.pem -CA ca-cert.pem -CAkey ca-key.pem \\\n  -CAcreateserial -out server-cert.pem -days 365\n<\/pre><\/div>\n\n\n\n<p>This creates a self-signed CA using its private key (<em>ca-key.pem<\/em>). Its validity is set to 365 days and saved in <em>ca-cert.pem<\/em>. It also:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Generates an X.509 certificate.<\/li>\n\n\n\n<li>Embeds metadata about the CA using the -subj flag.<ul><li>C: CountryST: StateL: City or localityO: Organization nameOU: Organizational unit<\/li><\/ul><div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>CN: Common Name (in our case, postgres, but the common name can be anything).<\/li>\n<\/ul>\n<\/div><\/li>\n<\/ul>\n<\/div>\n\n\n<p>If you omit the <em>-subj<\/em> flag, OpenSSL will ask you to provide the metadata for your CA. It\u2019s usually very convenient to provide them upfront with the <em>-subj<\/em> flag.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Create Client Certificate<\/h3>\n\n\n\n<p>This certificate will be used by a trusted client (such as a web app or a CI process) to authenticate.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" ># Server private key\nopenssl genrsa -out server-key.pem 4096\n\n# Certificate signing request:\nopenssl req -new -key server-key.pem -out server-req.pem \\\n  -subj \"\/C=US\/ST=State\/L=City\/O=MyOrg\/OU=Dev\/CN=postgres\"\n\n# Sign the request with your CA\nopenssl x509 -req -in server-req.pem -CA ca-cert.pem -CAkey ca-key.pem \\\n  -CAcreateserial -out server-cert.pem -days 365<\/pre><\/div>\n\n\n\n<h3 class=\"wp-block-heading\">Set File Permissions<\/h3>\n\n\n\n<p>Make sure these certificates are usable by Postgres, but not world-readable.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >chmod 600 server-key.pem client-key.pem ca-key.pem\nchmod 644 server-cert.pem client-cert.pem ca-cert.pem\n\n# PostgreSQL container uses UID 999 \u2014 assign ownership to the postgres user for server certificates:\nsudo chown 999:999 server-key.pem server-cert.pem\n\n# Let your host user keep access (you) forto the client certificates and CA certificate:certs\nsudo chown $(whoami):$(whoami) client-key.pem client-cert.pem ca-cert.pem<\/pre><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-step-2-configuring-postgresql-for-ssl-and-authentication\">Step 2: Configuring PostgreSQL for SSL and Authentication<\/h2>\n\n\n\n<p>We now have all the certificates we need, but PostgreSQL won\u2019t use them unless we tell it to \u2014 via configuration files. We&#8217;ll create two key files:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>postgresql.conf \u2014 controls server behaviour, including SSL and logging<\/li>\n\n\n\n<li>pg_hba.conf \u2014 controls authentication rules and client access<\/li>\n<\/ul>\n<\/div>\n\n\n<p>These will be mounted into the container at runtime, so Postgres picks them up on launch.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-create-the-config-directory\">Create the Config Directory<\/h3>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >cd ..        # Back to project root\nmkdir config\ncd config\n<\/pre><\/div>\n\n\n\n<h3 class=\"wp-block-heading\">Create pg_hba.conf<\/h3>\n\n\n\n<p>This file tells PostgreSQL which users can connect, from where, using which authentication method.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >cat &gt; pg_hba.conf &lt;&lt; 'EOF'\n# PostgreSQL Client Authentication Configuration File\n\n# TYPE  DATABASE  USER     ADDRESS     METHOD\n\n# Require client certificate for pguser\nhostssl all       pguser   0.0.0.0\/0   cert\n\n# Allow other users with password (SCRAM)\nhostssl all       all      0.0.0.0\/0   scram-sha-256\n\n# Local Unix socket access\nlocal   all       postgres             peer\nlocal   all       all                  peer\n\n# IPv4\/IPv6 localhost access\nhost    all       all      127.0.0.1\/32 scram-sha-256\nhost    all       all      ::1\/128      scram-sha-256\n\n# Explicitly reject non-SSL connections\nhost    all       all      0.0.0.0\/0   reject\nEOF<\/pre><\/div>\n\n\n\n<h3 class=\"wp-block-heading\">What&#8217;s going on here?<\/h3>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>The pguser role will authenticate using its client certificate \u2014 there&#8217;s no password required.<br><\/li>\n\n\n\n<li>Other users (like human users. The user <em>pguser<\/em> is for automated connections where passwords are inconvenient and less secure. Certificates provide stronger authentication for service-to-service communication, applications can\u2019t forget certificates, and they also can&#8217;t be easily intercepted like passwords. Humans (developers), on the other hand, can still use passwords, secured by SSL.<br><\/li>\n\n\n\n<li>Local socket access (e.g. via docker exec) is allowed with no password \u2013 a handy \u201cback door\u201d for manual admin tasks when you\u2019re inside the container.<\/li>\n<\/ul>\n<\/div>\n\n\n<p>It\u2019s a common and practical split: strong certificate-based auth for automated systems (they won\u2019t forget a cert), and simpler password auth for developers.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Create postgresql.conf<\/h3>\n\n\n\n<p>This is PostgreSQL\u2019s main configuration file. We\u2019ll use it to enable SSL and point to the right files.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >cat &gt; postgresql.conf &lt;&lt; 'EOF'\n# Enable SSL\nssl = on\nssl_cert_file = '\/certs\/server-cert.pem'\nssl_key_file = '\/certs\/server-key.pem'\nssl_ca_file = '\/certs\/ca-cert.pem'\n\n# Networking\nlisten_addresses = '*'\nport = 5432\n\n# Logging\nlog_connections = on\nlog_disconnections = on\n\n# File paths\nhba_file = '\/config\/pg_hba.conf'\ndata_directory = '\/pgdata'\nEOF<\/pre><\/div>\n\n\n\n<h3 class=\"wp-block-heading\">Why set listen_addresses = &#8216;*&#8217;?<\/h3>\n\n\n\n<p>This allows connections from any IP, which is useful in development or if other containers will connect via a custom location &#8211; <em>\/pgdata<\/em> instead of the container\u2019s default locationDocker network. In production, you&#8217;d narrow this.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Fix Permissions<\/h3>\n\n\n\n<p>PostgreSQL runs inside the container as UID 999, so it must own the config files. Otherwise, you\u2019ll likely run into permissions errors that stop the container from starting or accepting connection:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >sudo chown 999:999 postgresql.conf pg_hba.conf\nsudo chmod 644 postgresql.conf pg_hba.conf\n<\/pre><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-step-3-prepare-the-data-directory\">Step 3: Prepare the <a><\/a>Data Directory<\/h2>\n\n\n\n<p>Postgres needs a place to store databases, tables, logs, and so on \u2013 without it, Postgres will simply not operate. By default, it uses \/var\/lib\/postgresql\/data, but we\u2019re going to use a custom path and mount it as a Docker container, which will look for or initialize all its data under the <em>\/pgdata<\/em> directory inside the container. For your PostgreSQL container to use the custom <em>\/pgdata<\/em> directory, you need to create the directory (on your host machine) and ensure that only the postgres user inside the PostgreSQL container has access to the <em>\/pgata<\/em> directoryvolume.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >cd ..         # Back to project root\nmkdir pgdata\nsudo chown -R 999:999 pgdata\nsudo chmod 700 pgdata\n<\/pre><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Step 4: Launch the Postgres UserContainer<\/h2>\n\n\n\n<p><a><\/a>With certificates, configs, and data directory in place, it\u2019s time to spin up the container.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Add Yourself to the Docker Group (Optional)<\/h3>\n\n\n\n<p>So you can run Docker without sudo:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >sudo usermod -aG docker $USER\n# Then restart your shell for the command above to take effect.terminal session\n\n<\/pre><\/div>\n\n\n\n<h3 class=\"wp-block-heading\">Create a Docker Network<\/h3>\n\n\n\n<p>This allows Postgres to be easily connected to other containers (like an app or <a href=\"https:\/\/www.pgadmin.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">pgAdmin<\/a>):<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >docker network create postgres-network<\/pre><\/div>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-start-the-postgresql-container\">Start the PostgreSQL Container<\/h3>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >docker run -d \\\n  --name postgres \\\n  --network postgres-network \\\n  -e POSTGRES_PASSWORD=admin123 \\\n  -e PGDATA=\"\/pgdata\" \\\n  -v $(pwd)\/config:\/config \\\n  -v $(pwd)\/pgdata:\/pgdata \\\n  -v $(pwd)\/certs:\/certs \\\n  -p 5432:5432 \\\n  postgres:latest \\\n  -c 'config_file=\/config\/postgresql.conf'<\/pre><\/div>\n\n\n\n<p>This starts the container (with the name <em>postgres<\/em>) in detached mode. It also:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Uses our custom postgresql.conf and <em>pg_hba.conf<\/em> files.<br><\/li>\n\n\n\n<li>Mounts certs, configs, and data volumes.<br><\/li>\n\n\n\n<li>Makes Postgres accessible on port 5432.<br><\/li>\n\n\n\n<li>Sets the default postgres user password (required even if not used).<\/li>\n<\/ul>\n<\/div>\n\n\n<h3 class=\"wp-block-heading\" id=\"h-verify-it-s-running\">Verify It\u2019s Running<\/h3>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >docker ps<\/pre><\/div>\n\n\n\n<p>You should see something like this:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >nginx\nCONTAINER ID   IMAGE             ...   PORTS                    NAMES\nbe901fac6eb9   postgres:latest   ...   0.0.0.0:5432-&gt;5432\/tcp   postgres\n<\/pre><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-step-5-creating-database-users\">Step 5: Creating Database Users<\/h2>\n\n\n\n<p>Now that our PostgreSQL container is up, we need to create the database users that will match our authentication setup. We will Create `pguser` for cert-based service access and `john` for human password-based access.<\/p>\n\n\n\n<p>First, open a shell inside the container:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >docker exec -it postgres bash\nSwitch to the postgres user (the default superuser inside the container):\nsu - postgres\n<\/pre><\/div>\n\n\n\n<p>Open the PostgreSQL client:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >psql<\/pre><\/div>\n\n\n\n<p>Create two users \u2014 one for automated service-to-service connections (pguser, matching the CN in the client certificate), and one for general development (john):<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >BEGIN;\nCREATE USER pguser;\nCREATE USER john WITH PASSWORD 'john123';\nCOMMIT;<\/pre><\/div>\n\n\n\n<p>Exit psql and the container shell:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >\/q\nexit\nexit<\/pre><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-step-6-testing-ssl-connections\">Step 6: Testing SSL Connections<\/h2>\n\n\n\n<p>Let\u2019s confirm that pguser can connect using its certificate and that john can connect using a password over SSL.<\/p>\n\n\n\n<p>From the host machine, run:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >psql \"host=localhost port=5432 dbname=postgres user=pguser \\\nsslmode=require \\\nsslcert=certs\/client-cert.pem \\\nsslkey=certs\/client-key.pem \\\nsslrootcert=certs\/ca-cert.pem\"\n<\/pre><\/div>\n\n\n\n<p>You should see something like:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384)\nType \"help\" for help.\n\npostgres=&gt;\n<\/pre><\/div>\n\n\n\n<p>Notice that no password was requested \u2014 the client certificate handled authentication.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Test a Password-Based User<\/h3>\n\n\n\n<p>If you connect as john, the container will fall back to password authentication, but still require SSL:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >psql \"host=localhost port=5432 dbname=postgres user=john sslmode=require\"<\/pre><\/div>\n\n\n\n<p>You\u2019ll be prompted for john123 (or whatever password you set).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 7: Switching to Let\u2019s Encrypt (For Real-World Deployments)<\/h2>\n\n\n\n<p>Our self-signed certificates are fine for development, but production should use trusted certificates from a CA like <a href=\"https:\/\/letsencrypt.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">Let\u2019s Encrypt<\/a>. We\u2019ll end by showing how you\u2019d swap to Let\u2019s Encrypt in production.<\/p>\n\n\n\n<p>The workflow is very similar:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Use <a href=\"https:\/\/certbot.eff.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">certbot<\/a> (or an equivalent ACME client) to request your certificates.<br><\/li>\n\n\n\n<li>Replace the files server-cert.pem and server-key.pem in \/certs.<br><\/li>\n\n\n\n<li>Update postgresql.conf if the file paths change.<br><\/li>\n\n\n\n<li>Restart the container.<\/li>\n<\/ul>\n<\/div>\n\n\n<p>If you\u2019re running Postgres behind a reverse proxy or load balancer, you can often terminate SSL at that layer and connect to Postgres over a secure network, simplifying the setup.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Conclusion<\/h1>\n\n\n\n<p>If you\u2019ve followed this workflow, you should now have a containerized PostgreSQL instance with:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>SSL\/TLS encryption and certificate-based authentication.<br><\/li>\n\n\n\n<li>Custom pg_hba.conf and postgresql.conf for fine-grained control.<br><\/li>\n\n\n\n<li>Persistent volumes to keep your data across container runs.<br><\/li>\n\n\n\n<li>A clear path to production-grade certificates via Let\u2019s Encrypt.<\/li>\n<\/ul>\n<\/div>\n\n\n<p>You\u2019ve just built something much closer to a real-world PostgreSQL setup \u2014 SSL, cert-based auth, persistent data \u2014 but still with all the convenience of Docker. Not bad for a few shell commands. It\u2019s portable, reproducible, and secure enough to mirror a real-world environment, making your database development and testing much more relevant and meaningful.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Learn how to run PostgreSQL in Docker with production-grade SSL encryption, certificate-based authentication, and custom configuration files &#8211; perfect for secure development environments.&hellip;<\/p>\n","protected":false},"author":341730,"featured_media":107546,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143523,143534],"tags":[5869,159078,158978],"coauthors":[158989],"class_list":["post-107543","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-databases","category-postgresql","tag-databases","tag-docker","tag-postgresql"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/107543","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/341730"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=107543"}],"version-history":[{"count":2,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/107543\/revisions"}],"predecessor-version":[{"id":107547,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/107543\/revisions\/107547"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media\/107546"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=107543"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=107543"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=107543"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=107543"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}