<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Blue Elysium</title>
	<atom:link href="https://blueelysium.net/feed/" rel="self" type="application/rss+xml" />
	<link>https://blueelysium.net</link>
	<description></description>
	<lastBuildDate>Thu, 11 Dec 2025 00:23:21 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0</generator>

<image>
	<url>https://blueelysium.net/wp-content/uploads/2023/03/small_fields._ethereal_03.png</url>
	<title>Blue Elysium</title>
	<link>https://blueelysium.net</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>UDMPro Managing Lists</title>
		<link>https://blueelysium.net/2025/12/11/udmpro-managing-lists/</link>
					<comments>https://blueelysium.net/2025/12/11/udmpro-managing-lists/#respond</comments>
		
		<dc:creator><![CDATA[ffortunato]]></dc:creator>
		<pubDate>Thu, 11 Dec 2025 00:23:21 +0000</pubDate>
				<category><![CDATA[Technology]]></category>
		<guid isPermaLink="false">https://blueelysium.net/?p=943</guid>

					<description><![CDATA[Ever needed to manually edit lists outside of the ubiquiti interface. I had several long lists of IPs that I want to deny. I update this list based on IPs I gather from my router logs each month. Rather cutting and pasting them each time I wanted to upload a file. The lists are json [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Ever needed to manually edit lists outside of the ubiquiti interface.</p>



<p class="wp-block-paragraph">I had several long lists of IPs that I want to deny. I update this list based on IPs I gather from my router logs each month. Rather cutting and pasting them each time I wanted to upload a file. The lists are json files that are managed by mongodb. Here is how you can interact with them (insert / update / delete)</p>


<h2>Connection</h2>
<p>Access your ubiquiti router via ssh.</p>
<h3>SSH Access</h3>
<pre><code class="language-bash">ssh root@&lt;udm-ip&gt;</code></pre>
<h3>MongoDB Connection</h3>
<pre><code class="language-bash">mongo --port 27117</code></pre>
<p>MongoDB runs on port 27117 (not default 27017).</p>
<h3>Select Database</h3>
<pre><code class="language-javascript">use ace</code></pre>
<p>The UniFi database is named <code>ace</code>.</p>
<h2>Querying Data</h2>
<h3>View Firewall Groups</h3>
<pre><code class="language-javascript">// All firewall groups
db.firewallgroup.find().pretty()

// IP address groups only
db.firewallgroup.find({"group_type": "address-group"}).pretty()

// Port groups only
db.firewallgroup.find({"group_type": "port-group"}).pretty()

// IPv6 groups only
db.firewallgroup.find({"group_type": "ipv6-address-group"}).pretty()

// Summary view (names and members only)
db.firewallgroup.find({}, {name: 1, group_type: 1, group_members: 1, _id: 0}).pretty()</code></pre>
<h3>View Other Collections</h3>
<pre><code class="language-javascript">// User/Client groups
db.usergroup.find().pretty()

// Network configurations
db.networkconf.find().pretty()

// List all collections
show collections

// List all databases
show dbs</code></pre>
<h3>One-liner Queries (from SSH)</h3>
<pre><code class="language-bash">mongo --port 27117 ace --eval "db.firewallgroup.find().pretty()"</code></pre>
<h2>Updating Data</h2>
<h3>Basic Update Operations</h3>
<h4>Replace Array</h4>
<pre><code class="language-javascript">db.firewallgroup.updateOne(
  { "name": "YourGroupName" },
  { $set: { "group_members": ["192.168.1.10", "192.168.1.20", "192.168.1.30"] } }
)</code></pre>
<h4>Update Multiple Fields</h4>
<pre><code class="language-javascript">db.firewallgroup.updateOne(
  { "name": "YourGroupName" },
  { $set: { 
      "group_members": ["192.168.1.10", "192.168.1.20"],
      "group_members_ipv6": []
    } 
  }
)</code></pre>
<h4>Add Single Item to Array</h4>
<pre><code class="language-javascript">db.firewallgroup.updateOne(
  { "name": "YourGroupName" },
  { $addToSet: { "group_members": "192.168.1.50" } }
)</code></pre>
<h4>Add Multiple Items to Array</h4>
<pre><code class="language-javascript">db.firewallgroup.updateOne(
  { "name": "YourGroupName" },
  { $addToSet: { "group_members": { $each: ["192.168.1.50", "192.168.1.51"] } } }
)</code></pre>
<h4>Remove Item from Array</h4>
<pre><code class="language-javascript">db.firewallgroup.updateOne(
  { "name": "YourGroupName" },
  { $pull: { "group_members": "192.168.1.10" } }
)</code></pre>
<h3>MongoDB Update Operators</h3>
<table>
<thead>
<tr>
<th>Operator</th>
<th>Function</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>$set</code></td>
<td>Set field value(s)</td>
</tr>
<tr>
<td><code>$unset</code></td>
<td>Remove field</td>
</tr>
<tr>
<td><code>$addToSet</code></td>
<td>Add to array (no duplicates)</td>
</tr>
<tr>
<td><code>$push</code></td>
<td>Add to array (allows duplicates)</td>
</tr>
<tr>
<td><code>$pull</code></td>
<td>Remove from array</td>
</tr>
<tr>
<td><code>$inc</code></td>
<td>Increment number</td>
</tr>
</tbody>
</table>
<h2>Workflow</h2>
<h3>Recommended Update Sequence</h3>
<pre><code class="language-javascript">// 1. Backup current document
var backup = db.firewallgroup.findOne({"name": "YourGroupName"})
printjson(backup)

// 2. Verify query matches exactly one document
db.firewallgroup.find({"name": "YourGroupName"})

// 3. Execute update
db.firewallgroup.updateOne(
  { "name": "YourGroupName" },
  { $set: { "group_members": ["192.168.1.10", "192.168.1.20"] } }
)

// 4. Verify update result
// Look for: { "acknowledged": true, "matchedCount": 1, "modifiedCount": 1 }

// 5. Confirm changes
db.firewallgroup.findOne({"name": "YourGroupName"})</code></pre>
<h3>Backup Database</h3>
<pre><code class="language-bash">mongodump --port 27117 --db ace --out /root/backup-$(date +%Y%m%d)</code></pre>
<h3>Restart UniFi Service</h3>
<pre><code class="language-bash">systemctl restart unifi</code></pre>
<p>Restart required for controller to reload database changes.</p>
<h2>Notes</h2>
<ul>
<li>Enable SSH in Settings → System → Advanced</li>
<li>Direct database modifications may be overwritten by controller</li>
<li>Backup before modifications</li>
<li>Verify query scope before executing updates</li>
<li>Check update result for matchedCount and modifiedCount</li>
</ul>]]></content:encoded>
					
					<wfw:commentRss>https://blueelysium.net/2025/12/11/udmpro-managing-lists/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Building BlueElysium: Part 2 &#8211; The Mail Server Journey</title>
		<link>https://blueelysium.net/2025/11/25/building-blueelysium-part-2-the-mail-server-journey/</link>
					<comments>https://blueelysium.net/2025/11/25/building-blueelysium-part-2-the-mail-server-journey/#respond</comments>
		
		<dc:creator><![CDATA[ffortunato]]></dc:creator>
		<pubDate>Tue, 25 Nov 2025 05:56:18 +0000</pubDate>
				<category><![CDATA[Web Site Management]]></category>
		<guid isPermaLink="false">https://blueelysium.net/?p=931</guid>

					<description><![CDATA[From &#8220;How hard can it be?&#8221; to &#8220;Oh, that&#8217;s why people use Gmail&#8221; &#8211; A tale of SMTP, DKIM, and debugging Introduction After successfully building the web server foundation in Part 1, I was feeling confident. &#8220;Email can&#8217;t be that hard,&#8221; I thought. &#8220;It&#8217;s just SMTP and IMAP, right?&#8221; Narrator:&#160;It was, in fact, that hard. [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><em>From &#8220;How hard can it be?&#8221; to &#8220;Oh, that&#8217;s why people use Gmail&#8221; &#8211; A tale of SMTP, DKIM, and debugging</em></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="introduction">Introduction</h2>



<p class="wp-block-paragraph">After successfully building the web server foundation in Part 1, I was feeling confident. &#8220;Email can&#8217;t be that hard,&#8221; I thought. &#8220;It&#8217;s just SMTP and IMAP, right?&#8221;</p>



<p class="wp-block-paragraph"><strong>Narrator:</strong>&nbsp;<em>It was, in fact, that hard.</em></p>



<p class="wp-block-paragraph">This is the story of building a production-ready mail server, complete with authentication, encryption, spam filtering, virus scanning, and enough troubleshooting to humble any system administrator. But in the end, when that first email arrived in my inbox with a green checkmark and perfect DKIM signature, it was absolutely worth it.</p>



<h2 class="wp-block-heading" id="the-email-vision">The Email Vision</h2>



<p class="wp-block-paragraph"><strong>Goal:</strong>&nbsp;Self-hosted email server with:</p>



<ul class="wp-block-list">
<li>Full SMTP and IMAP support</li>



<li>Web-based email account management</li>



<li>DKIM signing for email authentication</li>



<li>SPF and DMARC for sender validation</li>



<li>Spam filtering (SpamAssassin)</li>



<li>Virus scanning (ClamAV)</li>



<li>Fail2Ban for intrusion prevention</li>



<li>SSL/TLS encryption everywhere</li>
</ul>



<p class="wp-block-paragraph"><strong>Reality Check:</strong>&nbsp;This took considerably longer than the web server.</p>



<h2 class="wp-block-heading" id="part-1-choosing-the-stack">Part 1: Choosing the Stack</h2>



<h3 class="wp-block-heading" id="the-research-phase">The Research Phase</h3>



<p class="wp-block-paragraph">I evaluated several options:</p>



<ol class="wp-block-list">
<li><strong>Postfix + Dovecot (manual)</strong> &#8211; Ultimate flexibility, maximum pain</li>



<li><strong>Mail-in-a-Box</strong> &#8211; Too automated, limited customization</li>



<li><strong>iRedMail</strong> &#8211; Good, but complex setup</li>



<li><strong>docker-mailserver</strong> &#8211; Perfect balance of automation and control</li>
</ol>



<p class="wp-block-paragraph"><strong>Winner:</strong>&nbsp;<a href="https://github.com/docker-mailserver/docker-mailserver">docker-mailserver</a>&nbsp;(DMS)</p>



<p class="wp-block-paragraph"><strong>Why?</strong></p>



<ul class="wp-block-list">
<li>Well-maintained and documented</li>



<li>Includes everything: Postfix, Dovecot, OpenDKIM, SpamAssassin, ClamAV, Fail2Ban</li>



<li>Integrates with existing Docker infrastructure</li>



<li>Active community support</li>



<li>Can use PostfixAdmin for web management</li>
</ul>



<h3 class="wp-block-heading" id="the-architecture">The Architecture</h3>



<pre class="wp-block-code has-subtle-background-background-color has-background" style="font-size:11px"><code>Internet (Port 25/587/465/993)
    ↓
Nginx (SSL termination for HTTPS, pass-through for mail)
    ↓
docker-mailserver (SMTP/IMAP)
    ↓
MySQL (PostfixAdmin database)
    ↓
PostfixAdmin (Web interface)
</code></pre>



<h2 class="wp-block-heading" id="part-2-initial-configuration">Part 2: Initial Configuration</h2>



<h3 class="wp-block-heading" id="step-1-dns-records">Step 1: DNS Records</h3>



<p class="wp-block-paragraph">Before even starting the containers, I needed proper DNS records.&nbsp;<strong>This is critical</strong>&nbsp;&#8211; email won&#8217;t work without them.</p>



<pre class="wp-block-code has-subtle-background-background-color has-background" style="font-size:11px"><code># A Records
mail.blueelysium.net        A       &#91;SERVER_IP]
mailadmin.blueelysium.net   A       &#91;SERVER_IP]

# MX Record (highest priority)
blueelysium.net             MX 10   mail.blueelysium.net.

# SPF Record (authorize this server to send email)
blueelysium.net             TXT     "v=spf1 mx ~all"

# DMARC Record (email authentication policy)
_dmarc.blueelysium.net      TXT     "v=DMARC1<em>; p=quarantine; rua=mailto:postmaster@blueelysium.net"</em>
</code></pre>



<p class="wp-block-paragraph"><strong>Note:</strong>&nbsp;DKIM record comes later after generating keys.</p>



<h3 class="wp-block-heading" id="step-2-docker-compose-configuration">Step 2: Docker Compose Configuration</h3>



<p class="wp-block-paragraph">Adding the mail server to docker-compose.yml:</p>



<pre class="wp-block-code has-subtle-background-background-color has-background" style="font-size:11px"><code>mailserver:
  image: ghcr.io/docker-mailserver/docker-mailserver:latest
  container_name: mailserver
  hostname: mail
  domainname: blueelysium.net
  ports:
    - "25:25"     <em># SMTP</em>
    - "143:143"   <em># IMAP</em>
    - "465:465"   <em># SMTPS</em>
    - "587:587"   <em># Submission</em>
    - "993:993"   <em># IMAPS</em>
  volumes:
    - ./docker-data/dms/mail-data:/var/mail
    - ./docker-data/dms/mail-state:/var/mail-state
    - ./docker-data/dms/mail-logs:/var/log/mail
    - ./docker-data/dms/config:/tmp/docker-mailserver
    - ./docker-data/certbot/conf:/etc/letsencrypt:ro
  environment:
    - ENABLE_SPAMASSASSIN=1
    - ENABLE_CLAMAV=1
    - ENABLE_FAIL2BAN=1
    - ENABLE_POSTGREY=0
    - ENABLE_AMAVIS=1
    - ONE_DIR=1
    - DMS_DEBUG=0
    - PERMIT_DOCKER=network
    - SSL_TYPE=letsencrypt
    - SSL_DOMAIN=mail.blueelysium.net
    - POSTFIX_MAILBOX_SIZE_LIMIT=0
    - POSTFIX_MESSAGE_SIZE_LIMIT=50000000
</code></pre>



<p class="wp-block-paragraph"><strong>Key Decisions:</strong></p>



<ul class="wp-block-list">
<li><strong>Shared SSL certificates</strong> with web server (Let&#8217;s Encrypt)</li>



<li><strong>All security features enabled</strong> from day one</li>



<li><strong>MySQL backend</strong> for virtual mailboxes (integration with PostfixAdmin)</li>



<li><strong>One directory mode</strong> for easier backups</li>
</ul>



<h3 class="wp-block-heading" id="step-3-postfixadmin-integration">Step 3: PostfixAdmin Integration</h3>



<p class="wp-block-paragraph">PostfixAdmin provides a web interface for managing email accounts, domains, and aliases.</p>



<pre class="wp-block-code has-subtle-background-background-color has-background" style="font-size:11px"><code>postfixadmin:
  image: postfixadmin/postfixadmin:latest
  container_name: postfixadmin
  ports:
    - "8080:80"
  environment:
    POSTFIXADMIN_DB_TYPE: mysqli
    POSTFIXADMIN_DB_HOST: db
    POSTFIXADMIN_DB_NAME: ${MYSQL_PFBE_DATABASE}
    POSTFIXADMIN_DB_USER: ${MYSQL_PFBE_USER}
    POSTFIXADMIN_DB_PASSWORD: ${MYSQL_PFBE_PASSWORD}
    POSTFIXADMIN_SETUP_PASSWORD: ${POSTFIXADMIN_SETUP_PASS}
  depends_on:
    - db
</code></pre>



<p class="wp-block-paragraph"><strong>Initial Setup:</strong></p>



<ol class="wp-block-list">
<li>Access <code>https://mailadmin.blueelysium.net:8080/setup.php</code></li>



<li>Create setup password hash</li>



<li>Create superadmin account</li>



<li>Add domain: <code>blueelysium.net</code></li>



<li>Create first mailbox: <code>frank@blueelysium.net</code></li>
</ol>



<h2 class="wp-block-heading" id="part-3-the-mysql-integration-challenge">Part 3: The MySQL Integration Challenge</h2>



<h3 class="wp-block-heading" id="the-problem">The Problem</h3>



<p class="wp-block-paragraph">docker-mailserver needs to query MySQL for:</p>



<ul class="wp-block-list">
<li>Virtual domains</li>



<li>Virtual mailboxes</li>



<li>Virtual aliases</li>



<li>User authentication</li>
</ul>



<p class="wp-block-paragraph">But it doesn&#8217;t include MySQL client libraries by default!</p>



<h3 class="wp-block-heading" id="the-solution-user-patchessh">The Solution: user-patches.sh</h3>



<p class="wp-block-paragraph">I created a custom startup script that runs after DMS initializes:</p>



<pre class="wp-block-code has-subtle-background-background-color has-background" style="font-size:11px"><code>#!/bin/bash
<em># docker-data/dms/config/user-patches.sh</em>

echo "Installing MySQL client libraries..."
apt-get update
apt-get install -y postfix-mysql dovecot-mysql

echo "Configuring Postfix virtual domains..."
cat &gt; /etc/postfix/mysql-virtual-mailbox-domains.cf &lt;&lt; 'EOF'
user = ${MYSQL_PFBE_USER}
password = ${MYSQL_PFBE_PASSWORD}
hosts = db
dbname = ${MYSQL_PFBE_DATABASE}
query = SELECT domain FROM domain WHERE domain='%s' AND active='1'
EOF

echo "Configuring Postfix virtual mailboxes..."
cat &gt; /etc/postfix/mysql-virtual-mailbox-maps.cf &lt;&lt; 'EOF'
user = ${MYSQL_PFBE_USER}
password = ${MYSQL_PFBE_PASSWORD}
hosts = db
dbname = ${MYSQL_PFBE_DATABASE}
query = SELECT maildir FROM mailbox WHERE username='%s' AND active='1'
EOF

echo "Configuring Postfix virtual aliases..."
cat &gt; /etc/postfix/mysql-virtual-alias-maps.cf &lt;&lt; 'EOF'
user = ${MYSQL_PFBE_USER}
password = ${MYSQL_PFBE_PASSWORD}
hosts = db
dbname = ${MYSQL_PFBE_DATABASE}
query = SELECT goto FROM alias WHERE address='%s' AND active='1'
EOF

<em># Update Postfix main.cf</em>
postconf -e "virtual_mailbox_domains = mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf"
postconf -e "virtual_mailbox_maps = mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf"
postconf -e "virtual_alias_maps = mysql:/etc/postfix/mysql-virtual-alias-maps.cf"

<em># Reload Postfix</em>
postfix reload

echo "MySQL integration complete!"
</code></pre>



<p class="wp-block-paragraph"><strong>Critical Learning:</strong>&nbsp;This script runs every time the container starts, ensuring configuration persists.</p>



<h2 class="wp-block-heading" id="part-4-dkim-configuration-drama">Part 4: DKIM Configuration Drama</h2>



<h3 class="wp-block-heading" id="what-is-dkim">What is DKIM?</h3>



<p class="wp-block-paragraph">DKIM (DomainKeys Identified Mail) cryptographically signs outgoing emails to prove they&#8217;re legitimate. Without it, emails often go to spam.</p>



<h3 class="wp-block-heading" id="generating-dkim-keys">Generating DKIM Keys</h3>



<pre class="wp-block-code"><code>docker exec mailserver setup config dkim domain blueelysium.net
</code></pre>



<p class="wp-block-paragraph">This creates:</p>



<ul class="wp-block-list">
<li>Private key: <code>/tmp/docker-mailserver/opendkim/keys/blueelysium.net/default.private</code></li>



<li>Public key: <code>/tmp/docker-mailserver/opendkim/keys/blueelysium.net/default.txt</code></li>
</ul>



<h3 class="wp-block-heading" id="the-first-dkim-crisis">The First DKIM Crisis</h3>



<p class="wp-block-paragraph"><strong>Symptom:</strong>&nbsp;Emails weren&#8217;t getting DKIM signatures</p>



<p class="wp-block-paragraph"><strong>Error in logs:</strong></p>



<pre class="wp-block-code" style="font-size:11px"><code>opendkim&#91;212]: error loading key 'mail._domainkey.blueelysium.net'
</code></pre>



<p class="wp-block-paragraph"><strong>Investigation:</strong></p>



<pre class="wp-block-code" style="font-size:11px"><code>docker exec mailserver cat /etc/opendkim/KeyTable
mail._domainkey.blueelysium.net blueelysium.net:mail:/etc/opendkim/keys/blueelysium.net/mail.private

docker exec mailserver ls /etc/opendkim/keys/blueelysium.net/
default.private  default.txt
</code></pre>



<p class="wp-block-paragraph"><strong>The Problem:</strong>&nbsp;The configuration was looking for selector&nbsp;<code>mail</code>, but the actual key was named&nbsp;<code>default</code>!</p>



<p class="wp-block-paragraph"><strong>The Fix:</strong></p>



<p class="wp-block-paragraph">Updated KeyTable:</p>



<pre class="wp-block-code" style="font-size:11px"><code>docker exec mailserver bash -c 'echo "default._domainkey.blueelysium.net blueelysium.net:default:/etc/opendkim/keys/blueelysium.net/default.private" &gt; /etc/opendkim/KeyTable'
</code></pre>



<p class="wp-block-paragraph">Updated SigningTable:</p>



<pre class="wp-block-code" style="font-size:11px"><code>docker exec mailserver bash -c 'echo "*@blueelysium.net default._domainkey.blueelysium.net" &gt; /etc/opendkim/SigningTable'
</code></pre>



<p class="wp-block-paragraph"><strong>Making it Persistent:</strong></p>



<p class="wp-block-paragraph">Created permanent config files in&nbsp;<code>docker-data/dms/config/opendkim/</code>:</p>



<pre class="wp-block-code" style="font-size:11px"><code>sudo bash -c 'cat &gt; docker-data/dms/config/opendkim/KeyTable &lt;&lt; "EOF"
default._domainkey.blueelysium.net blueelysium.net:default:/etc/opendkim/keys/blueelysium.net/default.private
EOF'

sudo bash -c 'cat &gt; docker-data/dms/config/opendkim/SigningTable &lt;&lt; "EOF"
*@blueelysium.net default._domainkey.blueelysium.net
EOF'
</code></pre>



<h3 class="wp-block-heading" id="publishing-dkim-dns-record">Publishing DKIM DNS Record</h3>



<pre class="wp-block-code" style="font-size:11px"><code>docker exec mailserver cat /etc/opendkim/keys/blueelysium.net/default.txt
</code></pre>



<p class="wp-block-paragraph">Output:</p>



<pre class="wp-block-code" style="font-size:11px"><code>default._domainkey IN TXT ( "v=DKIM1; h=sha256; k=rsa; "
"p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsks63VLLrZy0EJcDn5mp..."
"...rest of very long key..." )
</code></pre>



<p class="wp-block-paragraph">Added to DNS as single TXT record:</p>



<pre class="wp-block-code" style="font-size:11px"><code>default._domainkey.blueelysium.net    TXT    "v=DKIM1; h=sha256; k=rsa; p=MIIBIjANBgkq...COMPLETE_KEY...AQAB"
</code></pre>



<p class="wp-block-paragraph"><strong>Critical:</strong>&nbsp;Must include the ENTIRE public key in one record. I initially had a truncated version that caused validation failures.</p>



<p class="wp-block-paragraph"><strong>Verification:</strong></p>



<pre class="wp-block-code" style="font-size:11px"><code>docker exec mailserver opendkim-testkey -d blueelysium.net -s default -vvv
</code></pre>



<p class="wp-block-paragraph">Output:&nbsp;<code>key OK</code></p>



<p class="wp-block-paragraph"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f389.png" alt="🎉" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>First Success:</strong>&nbsp;Sent test email, received with DKIM signature verified!</p>



<h2 class="wp-block-heading" id="part-5-the-desktop-connection-mystery">Part 5: The Desktop Connection Mystery</h2>



<h3 class="wp-block-heading" id="the-problem-1">The Problem</h3>



<p class="wp-block-paragraph"><strong>Symptoms:</strong></p>



<ul class="wp-block-list">
<li>Laptop (different VLAN): <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Could connect to mail server</li>



<li>Desktop (different VLAN): <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Connection timeout on ports 993, 465, 587</li>



<li>Same desktop: <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Could access website (ports 80, 443)</li>
</ul>



<p class="wp-block-paragraph"><strong>What I Tried:</strong></p>



<ol class="wp-block-list">
<li><strong>Firewall rules on UniFi</strong> &#8211; Created allow rules, no change</li>



<li><strong>UFW on server</strong> &#8211; All ports already allowed</li>



<li><strong>IPS/Threat Management</strong> &#8211; Temporarily disabled, no change</li>



<li><strong>Windows Firewall</strong> &#8211; Disabled on desktop, still timing out</li>



<li><strong>Docker iptables</strong> &#8211; Verified forwarding rules correct</li>
</ol>



<h3 class="wp-block-heading" id="the-investigation">The Investigation</h3>



<p class="wp-block-paragraph">Used&nbsp;<code>tcpdump</code>&nbsp;on the server to watch network traffic:</p>



<pre class="wp-block-code" style="font-size:11px"><code><em># Monitor port 993 traffic</em>
sudo tcpdump -i any port 993 -nn

<em># Send test connection from desktop</em>
<em># Desktop: nc -v mail.blueelysium.net 993</em>
</code></pre>



<p class="wp-block-paragraph"><strong>What I Saw:</strong></p>



<pre class="wp-block-code" style="font-size:11px"><code>SYN packet from 10.0.6.91:52345 → server:993  ← Desktop sending request
Packet forwarded to Docker container          ← Reached the server
No SYN-ACK response                           ← Container not responding!
</code></pre>



<p class="wp-block-paragraph"><strong>The Smoking Gun:</strong>&nbsp;Packets were reaching the server and Docker container, but the container wasn&#8217;t responding. This pointed to something inside the mailserver container blocking the connection.</p>



<h3 class="wp-block-heading" id="the-fail2ban-revelation">The Fail2Ban Revelation</h3>



<p class="wp-block-paragraph">On a hunch, I checked Fail2Ban status:</p>



<pre class="wp-block-code" style="font-size:11px"><code>docker exec mailserver fail2ban-client status dovecot
</code></pre>



<p class="wp-block-paragraph">Output:</p>



<pre class="wp-block-code" style="font-size:11px"><code>Status for the jail: dovecot
|- Filter
|  |- Currently failed: 1
|  |- Total failed: 2
|  `- File list: /var/log/mail.log
`- Actions
   |- Currently banned: 1
   |- Total banned: 1
   `- Banned IP list: &lt;My LAN IP>
</code></pre>



<p class="wp-block-paragraph"><strong>BINGO!</strong>&nbsp;My desktop IP was banned by Fail2Ban!</p>



<p class="wp-block-paragraph"><strong>Why?</strong>&nbsp;I had been testing authentication multiple times during setup, triggering Fail2Ban&#8217;s &#8220;too many failed login attempts&#8221; protection.</p>



<h3 class="wp-block-heading" id="the-fix-part-1---unban">The Fix: Part 1 &#8211; Unban</h3>



<pre class="wp-block-code" style="font-size:11px"><code>docker exec mailserver fail2ban-client set dovecot unbanip &lt;My LAN IP>
</code></pre>



<p class="wp-block-paragraph"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Desktop immediately connected!</strong></p>



<h2 class="wp-block-heading" id="part-6-the-dovecot-authentication-puzzle">Part 6: The Dovecot Authentication Puzzle</h2>



<h3 class="wp-block-heading" id="the-next-challenge">The Next Challenge</h3>



<p class="wp-block-paragraph">Desktop could now connect, but authentication failed:</p>



<p class="wp-block-paragraph"><strong>Outlook Error:</strong>&nbsp;&#8220;Cannot connect to server. Verify username and password.&#8221;</p>



<p class="wp-block-paragraph"><strong>Server Logs:</strong></p>



<pre class="wp-block-code"><code>dovecot: auth-worker: Invalid password in passdb: Not a valid MD5-CRYPT or PLAIN-MD5 password
</code></pre>



<h3 class="wp-block-heading" id="understanding-the-problem">Understanding the Problem</h3>



<p class="wp-block-paragraph"><strong>PostfixAdmin</strong>&nbsp;stores passwords as SHA512-CRYPT hashes:</p>



<pre class="wp-block-code"><code>$6$rounds=5000$randomsalt$hashedpassword...
</code></pre>



<p class="wp-block-paragraph"><strong>Dovecot</strong>&nbsp;was trying to read them as PLAIN-MD5, which didn&#8217;t match.</p>



<h3 class="wp-block-heading" id="the-solution">The Solution</h3>



<p class="wp-block-paragraph">Modified Dovecot&#8217;s SQL configuration to handle the hash format:</p>



<pre class="wp-block-code" style="font-size:11px"><code>docker exec mailserver bash -c "cat &gt; /etc/dovecot/dovecot-sql.conf.ext &lt;&lt; 'EOF'
driver = mysql
connect = host=db dbname=pfbe user=pfuser password=SecurePassword
default_pass_scheme = CRYPT
password_query = SELECT username AS user, CONCAT('{CRYPT}', password) AS password FROM mailbox WHERE username='%u' AND active='1'
user_query = SELECT maildir AS home, 5000 AS uid, 5000 AS gid FROM mailbox WHERE username='%u' AND active='1'
EOF"
</code></pre>



<p class="wp-block-paragraph"><strong>Key Changes:</strong></p>



<ol class="wp-block-list">
<li><code>default_pass_scheme = CRYPT</code> &#8211; Tells Dovecot to expect crypt() format</li>



<li><code>CONCAT('{CRYPT}', password)</code> &#8211; Prefixes hash with {CRYPT} so Dovecot knows how to handle it</li>
</ol>



<p class="wp-block-paragraph"><strong>Added to user-patches.sh</strong>&nbsp;for persistence.</p>



<p class="wp-block-paragraph"><strong>Restarted mailserver:</strong></p>



<pre class="wp-block-code"><code>docker restart mailserver
</code></pre>



<p class="wp-block-paragraph"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Authentication successful!</strong>&nbsp;Could now send and receive emails!</p>



<h2 class="wp-block-heading" id="part-7-client-configuration">Part 7: Client Configuration</h2>



<h3 class="wp-block-heading" id="outlook-configuration">Outlook Configuration</h3>



<p class="wp-block-paragraph"><strong>IMAP Settings (Receiving):</strong></p>



<ul class="wp-block-list">
<li>Server: mail.blueelysium.net</li>



<li>Port: 993</li>



<li>Encryption: SSL/TLS</li>



<li>Username: <a href="mailto:frank@blueelysium.net">frank@blueelysium.net</a></li>



<li>Password: (from PostfixAdmin)</li>
</ul>



<p class="wp-block-paragraph"><strong>SMTP Settings (Sending):</strong></p>



<ul class="wp-block-list">
<li>Server: mail.blueelysium.net</li>



<li>Port: 587</li>



<li>Encryption: STARTTLS</li>



<li>Authentication: Required</li>



<li>Username: <a href="mailto:frank@blueelysium.net">frank@blueelysium.net</a></li>



<li>Password: (same as above)</li>
</ul>



<p class="wp-block-paragraph"><strong>Alternative SMTP:</strong></p>



<ul class="wp-block-list">
<li>Port: 465 with SSL/TLS also works</li>
</ul>



<h3 class="wp-block-heading" id="testing">Testing</h3>



<p class="wp-block-paragraph"><strong>Send test email:</strong></p>



<pre class="wp-block-code"><code>echo "Test email body" | mail -s "Test Subject" frank@blueelysium.net
</code></pre>



<p class="wp-block-paragraph"><strong>Check mail queue:</strong></p>



<pre class="wp-block-code"><code>docker exec mailserver postqueue -p
</code></pre>



<p class="wp-block-paragraph"><strong>View logs:</strong></p>



<pre class="wp-block-code"><code>docker logs mailserver --tail 50
</code></pre>



<p class="wp-block-paragraph"><strong>Check mailbox:</strong></p>



<pre class="wp-block-code"><code>docker exec mailserver ls -la /var/mail/blueelysium.net/frank/cur/
</code></pre>



<h2 class="wp-block-heading" id="part-8-fine-tuning--optimization">Part 8: Fine-Tuning &amp; Optimization</h2>



<h3 class="wp-block-heading" id="spamassassin-training">SpamAssassin Training</h3>



<pre class="wp-block-code"><code><em># Train on spam</em>
docker exec mailserver sa-learn --spam /var/mail/blueelysium.net/frank/.Junk/cur/*

<em># Train on ham (legitimate email)</em>
docker exec mailserver sa-learn --ham /var/mail/blueelysium.net/frank/cur/*

<em># Check stats</em>
docker exec mailserver sa-learn --dump magic
</code></pre>



<h3 class="wp-block-heading" id="clamav-database-updates">ClamAV Database Updates</h3>



<p class="wp-block-paragraph">ClamAV updates virus definitions automatically, but I verified:</p>



<pre class="wp-block-code"><code>docker exec mailserver freshclam
</code></pre>



<h3 class="wp-block-heading" id="monitoring-configuration">Monitoring Configuration</h3>



<p class="wp-block-paragraph">Created simple monitoring script:</p>



<pre class="wp-block-code"><code>#!/bin/bash
<em># check-mail.sh</em>

echo "=== Mail Server Status ==="
docker exec mailserver supervisorctl status

echo -e "\n=== Recent Auth Attempts ==="
docker logs mailserver | grep "auth" | tail -10

echo -e "\n=== Mail Queue ==="
docker exec mailserver postqueue -p

echo -e "\n=== Fail2Ban Status ==="
docker exec mailserver fail2ban-client status dovecot
docker exec mailserver fail2ban-client status postfix
</code></pre>



<h3 class="wp-block-heading" id="ssl-certificate-sharing">SSL Certificate Sharing</h3>



<p class="wp-block-paragraph">The mail server uses the same Let&#8217;s Encrypt certificates as the web server:</p>



<pre class="wp-block-code"><code>volumes:
  - ./docker-data/certbot/conf:/etc/letsencrypt:ro
</code></pre>



<p class="wp-block-paragraph"><strong>Important:</strong>&nbsp;After certificate renewal, restart mail server:</p>



<pre class="wp-block-code"><code>docker compose restart mailserver
</code></pre>



<p class="wp-block-paragraph">Added to renewal script for automation.</p>



<h2 class="wp-block-heading" id="part-9-testing--validation">Part 9: Testing &amp; Validation</h2>



<h3 class="wp-block-heading" id="email-deliverability-testing">Email Deliverability Testing</h3>



<p class="wp-block-paragraph"><strong>Tools Used:</strong></p>



<ol class="wp-block-list">
<li><strong>mail-tester.com</strong> &#8211; Comprehensive deliverability check</li>



<li><strong>MXToolbox</strong> &#8211; DNS and configuration validation</li>



<li><strong>Gmail</strong> &#8211; Send test email, check headers</li>
</ol>



<p class="wp-block-paragraph"><strong>Initial Score:</strong>&nbsp;8.5/10</p>



<p class="wp-block-paragraph"><strong>Issues Found:</strong></p>



<ul class="wp-block-list">
<li>Reverse DNS (PTR record) not set &#8211; Contacted ISP to set PTR record</li>



<li>DMARC record could be stronger &#8211; Updated to <code>p=quarantine</code></li>
</ul>



<p class="wp-block-paragraph"><strong>Final Score:</strong>&nbsp;10/10 <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f389.png" alt="🎉" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<h3 class="wp-block-heading" id="dkim-validation">DKIM Validation</h3>



<p class="wp-block-paragraph">Sent email to Gmail, viewed headers:</p>



<pre class="wp-block-code"><code>DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=blueelysium.net;
  s=default; t=1700000000;
  bh=abc123...;
  h=Date:From:To:Subject:From;
  b=xyz789...
dkim=pass (2048-bit key) header.d=blueelysium.net header.i=@blueelysium.net
</code></pre>



<p class="wp-block-paragraph"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>DKIM signing and verification working perfectly!</strong></p>



<h3 class="wp-block-heading" id="spf-validation">SPF Validation</h3>



<pre class="wp-block-code"><code>Received-SPF: pass (google.com: domain of frank@blueelysium.net designates &#91;SERVER_IP] as permitted sender)
</code></pre>



<p class="wp-block-paragraph"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>SPF validation passing!</strong></p>



<h3 class="wp-block-heading" id="load-testing">Load Testing</h3>



<p class="wp-block-paragraph">Sent 100 test emails to verify performance:</p>



<pre class="wp-block-code"><code>for i in {1..100}; do
  echo "Test email $i" | mail -s "Load Test $i" frank@blueelysium.net
done
</code></pre>



<p class="wp-block-paragraph"><strong>Results:</strong></p>



<ul class="wp-block-list">
<li>All 100 emails delivered</li>



<li>Average processing time: &lt;1 second per email</li>



<li>No performance degradation</li>



<li>CPU usage stayed under 30%</li>
</ul>



<h2 class="wp-block-heading" id="part-10-lessons-learned-the-hard-way">Part 10: Lessons Learned (The Hard Way)</h2>



<h3 class="wp-block-heading" id="1-dns-is-everything">1. DNS is Everything</h3>



<p class="wp-block-paragraph">Email is heavily dependent on DNS. </p>



<p class="wp-block-paragraph"><strong>Lesson:</strong>&nbsp;Set up ALL DNS records before starting, and verify with multiple tools.</p>



<h3 class="wp-block-heading" id="2-fail2ban-is-aggressive">2. Fail2Ban is Aggressive</h3>



<p class="wp-block-paragraph">Fail2Ban protected the server beautifully, but also banned me multiple times during testing.</p>



<p class="wp-block-paragraph"><strong>Lesson:</strong>&nbsp;Always whitelist internal networks before testing authentication.</p>



<h3 class="wp-block-heading" id="3-dkim-selector-names-matter">3. DKIM Selector Names Matter</h3>



<p class="wp-block-paragraph">The mismatch between&nbsp;<code>mail</code>&nbsp;and&nbsp;<code>default</code>&nbsp;cost me hours of debugging.</p>



<p class="wp-block-paragraph"><strong>Lesson:</strong>&nbsp;Check DKIM KeyTable and SigningTable match actual key filenames.</p>



<h3 class="wp-block-heading" id="4-password-hashing-is-tricky">4. Password Hashing is Tricky</h3>



<p class="wp-block-paragraph">PostfixAdmin&#8217;s SHA512-CRYPT format required specific Dovecot configuration.</p>



<p class="wp-block-paragraph"><strong>Lesson:</strong>&nbsp;Always specify&nbsp;<code>default_pass_scheme</code>&nbsp;and test authentication immediately.</p>



<h3 class="wp-block-heading" id="5-logs-are-your-friend">5. Logs are Your Friend</h3>



<p class="wp-block-paragraph">Every single issue was solved by reading logs carefully:</p>



<pre class="wp-block-code"><code>docker logs mailserver | tail -100
docker exec mailserver grep "error" /var/log/mail.log
</code></pre>



<p class="wp-block-paragraph"><strong>Lesson:</strong>&nbsp;When stuck, read the logs. Then read them again.</p>



<h3 class="wp-block-heading" id="6-network-troubleshooting-tools">6. Network Troubleshooting Tools</h3>



<p class="wp-block-paragraph"><code>tcpdump</code>&nbsp;was instrumental in discovering the Fail2Ban issue:</p>



<pre class="wp-block-code"><code>sudo tcpdump -i any port 993 -nn
</code></pre>



<p class="wp-block-paragraph"><strong>Lesson:</strong>&nbsp;Learn basic network debugging tools before starting.</p>



<h3 class="wp-block-heading" id="7-documentation-saves-time">7. Documentation Saves Time</h3>



<p class="wp-block-paragraph">Every fix I documented helped when similar issues appeared later.</p>



<p class="wp-block-paragraph"><strong>Lesson:</strong>&nbsp;Document everything, especially working configurations.</p>



<h2 class="wp-block-heading" id="part-11-the-backup-system">Part 11: The Backup System</h2>



<p class="wp-block-paragraph">With email working, data protection became critical. I built a comprehensive backup system:</p>



<p class="wp-block-paragraph"><strong>What Gets Backed Up:</strong></p>



<ul class="wp-block-list">
<li>All mailboxes (<code>/var/mail/</code>)</li>



<li>Mail server state and configuration</li>



<li>PostfixAdmin database</li>



<li>DKIM keys</li>



<li>SSL certificates</li>
</ul>



<p class="wp-block-paragraph"><strong>Backup Schedule:</strong></p>



<ul class="wp-block-list">
<li>Weekly full backups (Sunday 2:00 AM)</li>



<li>4-week retention</li>



<li>SHA256 integrity verification</li>
</ul>



<p class="wp-block-paragraph"><strong>Backup Script Highlights:</strong></p>



<pre class="wp-block-code"><code><em># Backup mail data</em>
tar czf mail_data.tar.gz -C /var/mail .

<em># Backup mail state</em>
tar czf mail_state.tar.gz -C /var/mail-state .

<em># Backup DKIM keys</em>
tar czf dkim_keys.tar.gz -C /tmp/docker-mailserver/opendkim .

<em># Backup configuration</em>
tar czf config.tar.gz docker-data/dms/config/
</code></pre>



<p class="wp-block-paragraph"><strong>Restore Testing:</strong></p>



<ul class="wp-block-list">
<li>Monthly restore verification</li>



<li>Complete disaster recovery procedures documented</li>
</ul>



<p class="wp-block-paragraph">Full documentation:&nbsp;<code>docs/BACKUP-RESTORE-GUIDE.md</code></p>



<h2 class="wp-block-heading" id="part-12-current-status--performance">Part 12: Current Status &amp; Performance</h2>



<h3 class="wp-block-heading" id="mail-server-stats-after-1-month">Mail Server Stats (After 1 Month)</h3>



<p class="wp-block-paragraph"><strong>Reliability:</strong></p>



<ul class="wp-block-list">
<li>Uptime: 99.9%</li>



<li>Email delivery success: 100%</li>



<li>Zero deliverability issues</li>



<li>No bounces or rejections</li>
</ul>



<p class="wp-block-paragraph"><strong>Security:</strong></p>



<ul class="wp-block-list">
<li>Fail2Ban: 15 IPs banned (external attackers)</li>



<li>SpamAssassin: 23 spam emails caught</li>



<li>ClamAV: 0 viruses detected (thankfully!)</li>



<li>SSL/TLS: All connections encrypted</li>
</ul>



<p class="wp-block-paragraph"><strong>Performance:</strong></p>



<ul class="wp-block-list">
<li>Email delivery: &lt;1 second</li>



<li>IMAP sync: Nearly instant</li>



<li>Resource usage: 200MB RAM, &lt;5% CPU</li>



<li>Storage: 82KB mail data (mostly test emails)</li>
</ul>



<h3 class="wp-block-heading" id="whats-working-brilliantly">What&#8217;s Working Brilliantly</h3>



<p class="wp-block-paragraph"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>DKIM signing</strong>&nbsp;&#8211; 100% verification rate<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>SPF validation</strong>&nbsp;&#8211; All emails pass<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>SSL/TLS encryption</strong>&nbsp;&#8211; A+ rating<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Spam filtering</strong>&nbsp;&#8211; Excellent catch rate<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Fail2Ban protection</strong>&nbsp;&#8211; Blocking attacks automatically<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Automated backups</strong>&nbsp;&#8211; Weekly, verified backups<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Client compatibility</strong>&nbsp;&#8211; Works with Outlook, Thunderbird, mobile</p>



<h3 class="wp-block-heading" id="what-could-be-better">What Could Be Better</h3>



<p class="wp-block-paragraph"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f538.png" alt="🔸" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Monitoring dashboard</strong>&nbsp;&#8211; Currently manual checks<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f538.png" alt="🔸" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Email retention policies</strong>&nbsp;&#8211; Not automated yet<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f538.png" alt="🔸" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Redundancy</strong>&nbsp;&#8211; Single server, no failover<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f538.png" alt="🔸" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Push notifications</strong>&nbsp;&#8211; Would need additional setup</p>



<h2 class="wp-block-heading" id="part-13-the-bigger-picture">Part 13: The Bigger Picture</h2>



<h3 class="wp-block-heading" id="why-self-host-email">Why Self-Host Email?</h3>



<p class="wp-block-paragraph"><strong>Advantages:</strong></p>



<ol class="wp-block-list">
<li><strong>Complete Privacy</strong> &#8211; My data, my server</li>



<li><strong>No Limits</strong> &#8211; No storage quotas or message limits</li>



<li><strong>Custom Domains</strong> &#8211; Professional email addresses</li>



<li><strong>Learning Experience</strong> &#8211; Deep understanding of email infrastructure</li>



<li><strong>Cost</strong> &#8211; Free after initial setup (besides server costs)</li>
</ol>



<p class="wp-block-paragraph"><strong>Disadvantages:</strong></p>



<ol class="wp-block-list">
<li><strong>Complexity</strong> &#8211; Requires technical knowledge</li>



<li><strong>Responsibility</strong> &#8211; You&#8217;re the sysadmin</li>



<li><strong>Deliverability</strong> &#8211; ISPs may be suspicious of self-hosted</li>



<li><strong>Maintenance</strong> &#8211; Updates and monitoring required</li>



<li><strong>No Support</strong> &#8211; You&#8217;re on your own for troubleshooting</li>
</ol>



<p class="wp-block-paragraph"><strong>My Verdict:</strong>&nbsp;Worth it for the learning and control, but not for everyone.</p>



<h3 class="wp-block-heading" id="skills-gained">Skills Gained</h3>



<p class="wp-block-paragraph">Through this project, I learned:</p>



<ul class="wp-block-list">
<li><strong>Email Protocols:</strong> SMTP, IMAP, POP3 in depth</li>



<li><strong>Authentication:</strong> DKIM, SPF, DMARC implementation</li>



<li><strong>Security:</strong> Fail2Ban, SSL/TLS, encryption</li>



<li><strong>Networking:</strong> DNS, iptables, Docker networking, tcpdump</li>



<li><strong>Troubleshooting:</strong> Log analysis, packet inspection, systematic debugging</li>



<li><strong>Database Integration:</strong> MySQL backend for virtual users</li>



<li><strong>Automation:</strong> Backup scripts, monitoring, maintenance</li>



<li><strong>Documentation:</strong> Writing technical documentation</li>
</ul>



<p class="wp-block-paragraph">These skills translate directly to professional DevOps/SysAdmin work.</p>



<h2 class="wp-block-heading" id="part-14-common-issues--solutions">Part 14: Common Issues &amp; Solutions</h2>



<h3 class="wp-block-heading" id="issue-1-email-goes-to-spam">Issue 1: Email Goes to Spam</h3>



<p class="wp-block-paragraph"><strong>Symptoms:</strong>&nbsp;Emails arrive in recipient&#8217;s spam folder</p>



<p class="wp-block-paragraph"><strong>Solutions:</strong></p>



<ol class="wp-block-list">
<li>Check DKIM signature: <code>opendkim-testkey</code></li>



<li>Verify SPF record: <code>dig blueelysium.net TXT</code></li>



<li>Add DMARC record</li>



<li>Set up reverse DNS (PTR record)</li>



<li>Test with mail-tester.com</li>



<li>Warm up IP (gradually increase send volume)</li>
</ol>



<h3 class="wp-block-heading" id="issue-2-cannot-receive-email">Issue 2: Cannot Receive Email</h3>



<p class="wp-block-paragraph"><strong>Symptoms:</strong>&nbsp;Outgoing works, but incoming fails</p>



<p class="wp-block-paragraph"><strong>Check:</strong></p>



<pre class="wp-block-code"><code><em># Verify MX record</em>
dig blueelysium.net MX

<em># Check if server is listening</em>
netstat -tulpn | grep ":25"

<em># Test SMTP connection</em>
telnet mail.blueelysium.net 25

<em># Check logs</em>
docker logs mailserver | grep "error"
</code></pre>



<p class="wp-block-paragraph"><strong>Common Causes:</strong></p>



<ul class="wp-block-list">
<li>Port 25 blocked by ISP (can&#8217;t be fixed easily)</li>



<li>Incorrect MX record</li>



<li>Firewall blocking port 25</li>



<li>Postfix not running</li>
</ul>



<h3 class="wp-block-heading" id="issue-3-authentication-failures">Issue 3: Authentication Failures</h3>



<p class="wp-block-paragraph"><strong>Symptoms:</strong>&nbsp;Client says &#8220;invalid password&#8221;</p>



<p class="wp-block-paragraph"><strong>Debug:</strong></p>



<pre class="wp-block-code"><code><em># Test authentication directly</em>
docker exec mailserver doveadm auth test frank@blueelysium.net

<em># Check password query</em>
docker exec mailserver mysql -h db -u pfuser -p -e "SELECT username, password FROM mailbox WHERE username='frank@blueelysium.net'"

<em># Review Dovecot SQL config</em>
docker exec mailserver cat /etc/dovecot/dovecot-sql.conf.ext
</code></pre>



<h3 class="wp-block-heading" id="issue-4-fail2ban-blocking-legitimate-users">Issue 4: Fail2Ban Blocking Legitimate Users</h3>



<p class="wp-block-paragraph"><strong>Symptoms:</strong>&nbsp;Sudden connection timeouts after several login attempts</p>



<p class="wp-block-paragraph"><strong>Fix:</strong></p>



<pre class="wp-block-code"><code><em># Check banned IPs</em>
docker exec mailserver fail2ban-client status dovecot

<em># Unban IP</em>
docker exec mailserver fail2ban-client set dovecot unbanip 10.0.6.91

<em># Add to whitelist (in fail2ban-jail.local)</em>
ignoreip = 10.0.6.0/24
</code></pre>



<h3 class="wp-block-heading" id="issue-5-disk-space-filling-up">Issue 5: Disk Space Filling Up</h3>



<p class="wp-block-paragraph"><strong>Symptoms:</strong>&nbsp;Server running out of space</p>



<p class="wp-block-paragraph"><strong>Check:</strong></p>



<pre class="wp-block-code"><code><em># Find large directories</em>
du -sh /var/mail/* | sort -h

<em># Check mail logs</em>
du -sh /var/log/mail/*

<em># ClamAV database</em>
du -sh /var/lib/clamav
</code></pre>



<p class="wp-block-paragraph"><strong>Solutions:</strong></p>



<ul class="wp-block-list">
<li>Implement mailbox quotas in PostfixAdmin</li>



<li>Rotate logs more aggressively</li>



<li>Clean old mail from test accounts</li>



<li>Set up automated cleanup scripts</li>
</ul>



<h3 class="wp-block-heading" id="the-webmail-question">The Webmail Question</h3>



<p class="wp-block-paragraph">I&#8217;m currently using desktop/mobile email clients, but considering adding webmail:</p>



<p class="wp-block-paragraph"><strong>Options:</strong></p>



<ul class="wp-block-list">
<li><strong>Roundcube</strong> &#8211; Feature-rich, but heavy</li>



<li><strong>SOGo</strong> &#8211; Includes calendar/contacts</li>



<li><strong>SnappyMail</strong> &#8211; Modern, fast, lightweight</li>
</ul>



<p class="wp-block-paragraph"><strong>Consideration:</strong>&nbsp;Do I need webmail if desktop/mobile apps work great?</p>



<h2 class="wp-block-heading" id="conclusion">Conclusion</h2>



<p class="wp-block-paragraph">Building a self-hosted mail server was significantly more challenging than the web server, but also more rewarding. Email is mission-critical infrastructure that most people never think about &#8211; until they try to run their own!</p>



<h3 class="wp-block-heading" id="the-stats">The Stats</h3>



<p class="wp-block-paragraph"><strong>Time Invested:</strong></p>



<ul class="wp-block-list">
<li>Initial research: 1 day</li>



<li>Setup and configuration: 2 days</li>



<li>Troubleshooting: 3 days (DNS, DKIM, Fail2Ban, authentication)</li>



<li>Documentation: 1 day</li>



<li><strong>Total: ~1 week</strong></li>
</ul>



<p class="wp-block-paragraph"><strong>Was It Worth It?</strong></p>



<p class="wp-block-paragraph"><strong>Sure,</strong> I now have:</p>



<p class="wp-block-paragraph"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Complete email independence<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Deep understanding of email infrastructure<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Professional email system (<a href="mailto:frank@blueelysium.net">frank@blueelysium.net</a>)<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Privacy and data ownership<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Impressive DevOps portfolio project<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Skills that transfer to professional work</p>



<h3 class="wp-block-heading" id="the-most-important-lesson">The Most Important Lesson</h3>



<p class="wp-block-paragraph"><strong>Email is hard for a reason.</strong>&nbsp;All the complexity &#8211; DKIM, SPF, DMARC, encryption, spam filtering &#8211; exists because email is a global, federated system fighting constant abuse. Self-hosting email isn&#8217;t just about running software; it&#8217;s about participating in a complex ecosystem built over decades.</p>



<p class="wp-block-paragraph">But once everything clicks into place and that first email arrives with perfect DKIM/SPF validation, green security checkmarks everywhere, and lands directly in the inbox (not spam!) &#8211; that feeling makes every hour of debugging worth it.</p>



<h2 class="wp-block-heading" id="resources-that-saved-me">Resources That Saved Me</h2>



<p class="wp-block-paragraph"><strong>Essential Documentation:</strong></p>



<ul class="wp-block-list">
<li><a href="https://docker-mailserver.github.io/docker-mailserver/latest/">docker-mailserver Documentation</a></li>



<li><a href="https://postfixadmin.github.io/postfixadmin/">PostfixAdmin Documentation</a></li>



<li><a href="http://www.postfix.org/documentation.html">Postfix Documentation</a></li>



<li><a href="https://doc.dovecot.org/">Dovecot Documentation</a></li>
</ul>



<p class="wp-block-paragraph"><strong>Testing Tools:</strong></p>



<ul class="wp-block-list">
<li><a href="https://www.mail-tester.com/">mail-tester.com</a> &#8211; Deliverability testing</li>



<li><a href="https://mxtoolbox.com/">MXToolbox</a> &#8211; DNS and email diagnostics</li>



<li><a href="https://dmarcian.com/">dmarcian</a> &#8211; DMARC testing</li>
</ul>



<p class="wp-block-paragraph"><strong>Community Resources:</strong></p>



<ul class="wp-block-list">
<li>docker-mailserver GitHub Issues (so many similar problems!)</li>



<li>Stack Overflow (specific DKIM/Dovecot questions)</li>



<li>Reddit r/selfhosted (moral support!)</li>
</ul>



<h2 class="wp-block-heading" id="final-thoughts">Final Thoughts</h2>



<p class="wp-block-paragraph">Self-hosting a mail server in 2025 is an anachronism. Gmail, Outlook.com, and other hosted services are free, reliable, and require zero maintenance. Most sysadmins will tell you it&#8217;s not worth the effort.</p>



<p class="wp-block-paragraph">But for the learning experience alone, I&#8217;m glad I did it. I now understand email at a level most folks never will. When someone mentions DKIM or SPF, I don&#8217;t just know what they are &#8211; I&#8217;ve configured them, debugged them, and watched them work in production.</p>



<p class="wp-block-paragraph">Plus, there&#8217;s something deeply satisfying about having&nbsp;<code>frank@blueelysium.net</code>&nbsp;as my email address, running on hardware I control, with no third-party reading my messages or selling my data.</p>



<p class="wp-block-paragraph"><strong>Would I recommend this to others?</strong></p>



<ul class="wp-block-list">
<li><strong>If you want to learn:</strong> Absolutely. It&#8217;s educational.</li>



<li><strong>If you need reliable email:</strong> Probably not. Use Gmail.</li>



<li><strong>If you value privacy:</strong> Maybe. But be prepared for the commitment.</li>
</ul>



<p class="wp-block-paragraph">Email is the last piece of self-hosted infrastructure I needed. Combined with the WordPress site from Part 1, I now have a complete, production-ready platform for web and email, entirely under my control.</p>



<p class="wp-block-paragraph">The journey from &#8220;How hard can email be?&#8221; to &#8220;Wow, email is complex!&#8221; taught me more than a dozen courses could have.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph"><strong>Thanks for reading!</strong>&nbsp;Questions? Want to share your own self-hosting journey? Reach out at&nbsp;<a href="mailto:frank@blueelysium.net">frank@blueelysium.net</a>&nbsp;(yes, it works! <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f604.png" alt="😄" class="wp-smiley" style="height: 1em; max-height: 1em;" />)</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph"><strong>Published:</strong>&nbsp;November 2025<br><strong>Author:</strong>&nbsp;Frank @ BlueElysium<br><strong>Series:</strong>&nbsp;Building a Self-Hosted Web &amp; Mail Server<br><strong>Part:</strong>&nbsp;2 of 2</p>



<p class="wp-block-paragraph"><strong>Read Part 1:</strong>&nbsp;<a href="https://file+.vscode-resource.vscode-cdn.net/c%3A/Users/frankf/source/BlueElysium/docs/blog/PART-1-BUILDING-THE-WEB-SERVER.md">Building the Web Server Foundation</a></p>



<p class="wp-block-paragraph"><strong>Repository:</strong>&nbsp;Full configuration files and documentation available<br><strong>Documentation:</strong>&nbsp;See&nbsp;<code>docs/</code>&nbsp;directory for detailed guides</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blueelysium.net/2025/11/25/building-blueelysium-part-2-the-mail-server-journey/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Building BlueElysium: Part 1 &#8211; The Web Server Foundation</title>
		<link>https://blueelysium.net/2025/11/23/building-blueelysium-part-1-the-web-server-foundation/</link>
					<comments>https://blueelysium.net/2025/11/23/building-blueelysium-part-1-the-web-server-foundation/#respond</comments>
		
		<dc:creator><![CDATA[ffortunato]]></dc:creator>
		<pubDate>Sun, 23 Nov 2025 05:52:30 +0000</pubDate>
				<category><![CDATA[Web Site Management]]></category>
		<guid isPermaLink="false">https://blueelysium.net/?p=926</guid>

					<description><![CDATA[A journey from concept to production: Building a self-hosted WordPress website with Docker Introduction When I decided to build my own self-hosted website and email infrastructure, I knew I wanted complete control over my data, flexibility to customize everything, and a learning experience that would deepen my understanding of modern web technologies. This is the [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><em>A journey from concept to production: Building a self-hosted WordPress website with Docker</em></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="introduction">Introduction</h2>



<p class="wp-block-paragraph">When I decided to build my own self-hosted website and email infrastructure, I knew I wanted complete control over my data, flexibility to customize everything, and a learning experience that would deepen my understanding of modern web technologies. This is the story of building <strong>BlueElysium</strong> &#8211; a fully containerized, production-ready web and mail server.</p>



<p class="wp-block-paragraph">In this first part, I&#8217;ll walk you through building the web server foundation: WordPress, MySQL, Nginx reverse proxy, and automated SSL certificate management.</p>



<h2 class="wp-block-heading" id="the-vision">The Vision</h2>



<p class="wp-block-paragraph"><strong>Goal:</strong> Create a self-hosted WordPress website with:</p>



<ul class="wp-block-list">
<li>Automated SSL certificate management</li>



<li>Production-grade security</li>



<li>Easy maintenance and updates</li>



<li>Scalable architecture for future expansion</li>



<li>Complete data ownership</li>
</ul>



<p class="wp-block-paragraph"><strong>Technology Stack:</strong></p>



<ul class="wp-block-list">
<li><strong>Docker &amp; Docker Compose</strong> for containerization</li>



<li><strong>WordPress</strong> with PHP 8.2-FPM</li>



<li><strong>MySQL 8.0</strong> for database</li>



<li><strong>Nginx</strong> as reverse proxy and web server</li>



<li><strong>Let&#8217;s Encrypt</strong> for SSL/TLS certificates via Certbot</li>
</ul>



<h2 class="wp-block-heading" id="part-1-the-infrastructure-design">Part 1: The Infrastructure Design</h2>



<h3 class="wp-block-heading" id="why-docker-">Why Docker?</h3>



<p class="wp-block-paragraph">I chose Docker for several compelling reasons:</p>



<ol class="wp-block-list">
<li><strong>Isolation</strong>: Each service runs in its own container with defined resources</li>



<li><strong>Reproducibility</strong>: The entire stack can be rebuilt from docker-compose.yml</li>



<li><strong>Portability</strong>: Easy to migrate to another server if needed</li>



<li><strong>Updates</strong>: Update individual services without affecting others</li>



<li><strong>Scalability</strong>: Add new services (like email) without disrupting existing ones</li>
</ol>



<h3 class="wp-block-heading" id="architecture-overview">Architecture Overview</h3>



<pre class="wp-block-code"><code>Internet → Port 80 443 → Nginx (webserver) → WordPress (PHP-FPM) → MySQL
                            ↓
                     Let's Encrypt (certbot)</code></pre>



<p class="wp-block-paragraph">The architecture consists of four core containers:</p>



<ol class="wp-block-list">
<li><strong>MySQL Database</strong> &#8211; Backend storage for WordPress</li>



<li><strong>WordPress</strong> &#8211; The CMS with PHP-FPM</li>



<li><strong>Nginx (webserver)</strong> &#8211; Reverse proxy with SSL termination</li>



<li><strong>Certbot</strong> &#8211; Automated SSL certificate management</li>
</ol>



<h2 class="wp-block-heading" id="part-2-setting-up-the-foundation">Part 2: Setting Up the Foundation</h2>



<h3 class="wp-block-heading" id="step-1-project-structure">Step 1: Project Structure</h3>



<p class="wp-block-paragraph">I organized the project with persistent data separated from container definitions:</p>



<pre class="wp-block-code"><code>BlueElysium/
├── docker-compose.yml          # Container orchestration
├── .env                        # Sensitive configuration
├── docker-data/                # Persistent data volumes
│   ├── wordpress/
│   │   └── html/              # WordPress files
│   ├── mysql/                 # Database files
│   └── certbot/               # SSL certificates
└── nginx-conf/                # Nginx configuration
    └── default.conf           # Reverse proxy config</code></pre>



<h3 class="wp-block-heading" id="step-2-the-database-container">Step 2: The Database Container</h3>



<p class="wp-block-paragraph">MySQL is the heart of the WordPress installation. Here&#8217;s what I learned about configuring it:</p>



<p class="wp-block-paragraph"><strong>Key Decisions:</strong></p>



<ul class="wp-block-list">
<li>Used MySQL 8.0 for better performance and security features</li>



<li>Created separate databases for WordPress and future mail server</li>



<li>Configured for UTF-8mb4 to support all Unicode characters</li>



<li>Set up health checks to ensure database is ready before WordPress starts</li>
</ul>



<p class="wp-block-paragraph"><strong>Important Configuration:</strong></p>



<pre class="wp-block-code"><code>db:
    image: mysql:8.0
    container_name: db
    restart: unless-stopped
    env_file: .env
    environment:
      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
      - MYSQL_DATABASE=${MYSQL_WPBE_DATABASE}
      - MYSQL_USER=${MYSQL_WPBE_USER}
      - MYSQL_PASSWORD=${MYSQL_WPBE_PASSWORD}
    volumes:
      - dbdata:/var/lib/mysql
      - ./docker/provision/mysql/init:/docker-entrypoint-initdb.d
    command: '--default-authentication-plugin=mysql_native_password'
    #command: '--default-authentication-plugin=caching_sha256_password'
    healthcheck:
      test: &#91;"CMD", "mysqladmin", "ping", "-h", "localhost"]
      timeout: 20s
      retries: 4
    networks:
      - app-network</code></pre>



<p class="wp-block-paragraph"><strong>Lesson Learned:</strong> Always use health checks! This prevents WordPress from trying to connect before MySQL is ready, eliminating those frustrating &#8220;Error establishing database connection&#8221; messages during startup.</p>



<h3 class="wp-block-heading" id="step-3-wordpress-container">Step 3: WordPress Container</h3>



<p class="wp-block-paragraph">WordPress runs as a separate container with PHP-FPM, communicating with Nginx via FastCGI.</p>



<p class="wp-block-paragraph"><strong>Key Configurations:</strong></p>



<pre class="wp-block-code"><code>wordpress:
    depends_on:
      db:
        condition: service_healthy
    image: wordpress:6.4.3-fpm-alpine
    container_name: wordpress
    restart: unless-stopped
    env_file: .env
    environment:
      - WORDPRESS_DB_HOST=db:3306
      - WORDPRESS_DB_USER=${MYSQL_WPBE_USER}
      - WORDPRESS_DB_PASSWORD=${MYSQL_WPBE_PASSWORD}
      - WORDPRESS_DB_NAME=${MYSQL_WPBE_DATABASE}
    volumes:
      - wordpress:/var/www/html
    healthcheck:
      test: &#91;"CMD-SHELL", "php-fpm -t || exit 1"]
      timeout: 10s
      retries: 4
      interval: 20s
    networks:
      - app-network</code></pre>



<p class="wp-block-paragraph"><strong>Why PHP-FPM?</strong></p>



<ul class="wp-block-list">
<li>Better performance than Apache mod_php</li>



<li>Separates web server (Nginx) from PHP processing</li>



<li>More granular resource control</li>



<li>Industry standard for production WordPress</li>
</ul>



<p class="wp-block-paragraph"><strong>Challenge:</strong> Getting WordPress and Nginx to communicate properly took some trial and error. The key was ensuring they shared the WordPress files volume and using the correct FastCGI parameters.</p>



<h3 class="wp-block-heading" id="step-4-nginx-reverse-proxy">Step 4: Nginx Reverse Proxy</h3>



<p class="wp-block-paragraph">Nginx handles incoming HTTP/HTTPS requests and routes them to WordPress.</p>



<p class="wp-block-paragraph"><strong>Configuration Highlights:</strong></p>



<pre class="wp-block-code"><code>server {
        listen 80 default_server;
        listen &#91;::]:80 default_server;

        server_name blueelysium.net www.blueelysium.net;

        location ~ /.well-known/acme-challenge {
                allow all;
                root /var/www/html;
        }
...</code></pre>



<p class="wp-block-paragraph"><strong>Critical Lessons:</strong></p>



<ol class="wp-block-list">
<li><strong>Shared Volumes</strong>: Nginx needs access to WordPress files for static assets</li>



<li><strong>FastCGI Path</strong>: Must use correct SCRIPT_FILENAME path</li>



<li><strong>Security Headers</strong>: Always include HSTS and other security headers</li>



<li><strong>HTTP/2</strong>: Significantly improves performance for modern browsers</li>
</ol>



<h3 class="wp-block-heading" id="step-5-ssl-certificate-automation">Step 5: SSL Certificate Automation</h3>



<p class="wp-block-paragraph">Let&#8217;s Encrypt provides free SSL certificates, but they expire every 90 days. Automation is essential.</p>



<p class="wp-block-paragraph"><strong>Certbot Container:</strong></p>



<pre class="wp-block-code"><code>certbot:
  image: certbot/certbot:latest
  volumes:
    - ./docker-data/certbot/conf:/etc/letsencrypt
    - ./docker-data/certbot/www:/var/www/certbot
  entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h &amp; wait $${!}; done;'"</code></pre>



<p class="wp-block-paragraph"><strong>Initial Certificate Request:</strong></p>



<pre class="wp-block-code"><code>docker compose run --rm certbot certonly \
  --webroot \
  --webroot-path=/var/www/certbot \
  --email admin@blueelysium.net \
  --agree-tos \
  --no-eff-email \
  -d blueelysium.net \
  -d www.blueelysium.net</code></pre>



<p class="wp-block-paragraph"><strong>The Chicken-and-Egg Problem:</strong></p>



<ul class="wp-block-list">
<li>Nginx needs certificates to start with SSL</li>



<li>Certbot needs Nginx running to verify domain ownership</li>
</ul>



<p class="wp-block-paragraph"><strong>Solution:</strong></p>



<ol class="wp-block-list">
<li>Start with HTTP-only Nginx configuration</li>



<li>Request certificates</li>



<li>Update Nginx config to use SSL</li>



<li>Restart Nginx</li>
</ol>



<p class="wp-block-paragraph"><strong>Pro Tip:</strong> Use the webroot method instead of standalone. This allows Nginx to keep running during renewals.</p>



<h2 class="wp-block-heading" id="part-3-environment-variables-security">Part 3: Environment Variables &amp; Security</h2>



<h3 class="wp-block-heading" id="the-env-file">The .env File</h3>



<p class="wp-block-paragraph">Never hardcode secrets! I used environment variables for all sensitive data:</p>



<pre class="wp-block-code"><code># Domain Configuration
DOMAIN=blueelysium.net
CERTBOT_EMAIL=admin@blueelysium.net

# MySQL Configuration
MYSQL_ROOT_PASSWORD=SecureRootPassword123!
MYSQL_WPBE_DATABASE=mydb
MYSQL_WPBE_USER=myuser
MYSQL_WPBE_PASSWORD=SecureWPPassword456!</code></pre>



<p class="wp-block-paragraph"><strong>Security Practices:</strong></p>



<ol class="wp-block-list">
<li><strong>Never commit .env to git</strong> &#8211; Add to .gitignore immediately</li>



<li><strong>Use strong passwords</strong> &#8211; 20+ characters, mixed case, numbers, symbols</li>



<li><strong>Different passwords</strong> &#8211; Each service gets unique credentials</li>



<li><strong>Backup .env securely</strong> &#8211; Store encrypted copy off-server</li>
</ol>



<h3 class="wp-block-heading" id="firewall-configuration">Firewall Configuration</h3>



<p class="wp-block-paragraph">I configured UFW (Uncomplicated Firewall) to allow only necessary ports:</p>



<pre class="wp-block-code"><code>sudo ufw allow 80/tcp   # HTTP
sudo ufw allow 443/tcp  # HTTPS
sudo ufw allow 22/tcp   # SSH
sudo ufw enable</code></pre>



<p class="wp-block-paragraph"><strong>Important:</strong> Configure SSH access BEFORE enabling the firewall!</p>



<h2 class="wp-block-heading" id="part-4-deployment-testing">Part 4: Deployment &amp; Testing</h2>



<h3 class="wp-block-heading" id="first-deployment">First Deployment</h3>



<pre class="wp-block-code"><code># Start all services
docker compose up -d

# Check status
docker compose ps

# View logs
docker compose logs -f</code></pre>



<p class="wp-block-paragraph"><strong>What I Watched For:</strong></p>



<ol class="wp-block-list">
<li>MySQL health check passing</li>



<li>WordPress connecting to database</li>



<li>Nginx starting without errors</li>



<li>Certificates being issued successfully</li>
</ol>



<h3 class="wp-block-heading" id="initial-wordpress-setup">Initial WordPress Setup</h3>



<p class="wp-block-paragraph">After containers were running, I accessed <code>https://blueelysium.net</code> and completed WordPress installation:</p>



<ol class="wp-block-list">
<li>Selected language</li>



<li>Created admin account (strong password!)</li>



<li>Set site title and tagline</li>



<li>Configured permalink structure (Post name for SEO)</li>
</ol>



<h3 class="wp-block-heading" id="performance-testing">Performance Testing</h3>



<p class="wp-block-paragraph">I used several tools to verify everything was working optimally:</p>



<p class="wp-block-paragraph"><strong>SSL/TLS Configuration:</strong></p>



<pre class="wp-block-code"><code># Test SSL
curl -I https://blueelysium.net

# Check certificate
openssl s_client -connect blueelysium.net:443 -servername blueelysium.net</code></pre>



<p class="wp-block-paragraph"><strong>Results:</strong></p>



<ul class="wp-block-list">
<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> A+ rating on SSL Labs</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> TLS 1.2 and 1.3 only</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Strong cipher suites</li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> HSTS enabled</li>
</ul>



<p class="wp-block-paragraph"><strong>Page Load Speed:</strong></p>



<ul class="wp-block-list">
<li>Initial: ~2.5 seconds</li>



<li>With caching plugins: ~800ms</li>



<li>With CDN consideration for future</li>
</ul>



<h2 class="wp-block-heading" id="part-5-common-issues-solutions">Part 5: Common Issues &amp; Solutions</h2>



<h3 class="wp-block-heading" id="issue-1-error-establishing-database-connection-">Issue 1: &#8220;Error Establishing Database Connection&#8221;</h3>



<p class="wp-block-paragraph"><strong>Problem:</strong> WordPress couldn&#8217;t connect to MySQL</p>



<p class="wp-block-paragraph"><strong>Root Cause:</strong> WordPress container started before MySQL was ready</p>



<p class="wp-block-paragraph"><strong>Solution:</strong> Added health check to database and <code>depends_on</code> with <code>condition: service_healthy</code></p>



<h3 class="wp-block-heading" id="issue-2-502-bad-gateway">Issue 2: 502 Bad Gateway</h3>



<p class="wp-block-paragraph"><strong>Problem:</strong> Nginx showed 502 error when accessing WordPress</p>



<p class="wp-block-paragraph"><strong>Root Cause:</strong> FastCGI configuration incorrect</p>



<p class="wp-block-paragraph"><strong>Solution:</strong></p>



<pre class="wp-block-code"><code>fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;</code></pre>



<p class="wp-block-paragraph">Must use <code>$document_root</code> not hardcoded path.</p>



<h3 class="wp-block-heading" id="issue-3-static-assets-not-loading">Issue 3: Static Assets Not Loading</h3>



<p class="wp-block-paragraph"><strong>Problem:</strong> Images and CSS files returned 404</p>



<p class="wp-block-paragraph"><strong>Root Cause:</strong> Nginx didn&#8217;t have access to WordPress files</p>



<p class="wp-block-paragraph"><strong>Solution:</strong> Shared WordPress volume with Nginx:</p>



<pre class="wp-block-code"><code>webserver:
  volumes:
    - ./docker-data/wordpress/html:/var/www/html:ro</code></pre>



<h3 class="wp-block-heading" id="issue-4-certificate-renewal-failed">Issue 4: Certificate Renewal Failed</h3>



<p class="wp-block-paragraph"><strong>Problem:</strong> Let&#8217;s Encrypt couldn&#8217;t verify domain ownership</p>



<p class="wp-block-paragraph"><strong>Root Cause:</strong> Nginx blocked /.well-known/acme-challenge/ path</p>



<p class="wp-block-paragraph"><strong>Solution:</strong> Added specific location block for ACME challenges:</p>



<pre class="wp-block-code"><code>location /.well-known/acme-challenge/ {
    root /var/www/certbot;
}</code></pre>



<h2 class="wp-block-heading" id="part-6-optimization-fine-tuning">Part 6: Optimization &amp; Fine-Tuning</h2>



<h3 class="wp-block-heading" id="wordpress-optimizations">WordPress Optimizations</h3>



<p class="wp-block-paragraph"><strong>Plugins Installed:</strong></p>



<ul class="wp-block-list">
<li><strong>WP Super Cache</strong> &#8211; Page caching for better performance</li>



<li><strong>Wordfence Security</strong> &#8211; Security scanning and firewall</li>



<li><strong>UpdraftPlus</strong> &#8211; Additional backup option</li>



<li><strong>Yoast SEO</strong> &#8211; Search engine optimization</li>
</ul>



<p class="wp-block-paragraph"><strong>Configuration Tweaks:</strong></p>



<pre class="wp-block-code"><code>// wp-config.php additions
define('WP_CACHE', true);
define('COMPRESS_CSS', true);
define('COMPRESS_SCRIPTS', true);
define('CONCATENATE_SCRIPTS', true);
define('ENFORCE_GZIP', true);</code></pre>



<h3 class="wp-block-heading" id="nginx-optimizations">Nginx Optimizations</h3>



<p class="wp-block-paragraph">Added caching headers for static assets:</p>



<pre class="wp-block-code"><code>location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
}</code></pre>



<h3 class="wp-block-heading" id="mysql-tuning">MySQL Tuning</h3>



<p class="wp-block-paragraph">Adjusted MySQL configuration for better performance:</p>



<pre class="wp-block-code"><code>&#91;mysqld]
innodb_buffer_pool_size = 512M
max_connections = 100
query_cache_size = 0
query_cache_type = 0</code></pre>



<h2 class="wp-block-heading" id="part-7-monitoring-maintenance">Part 7: Monitoring &amp; Maintenance</h2>



<h3 class="wp-block-heading" id="daily-monitoring">Daily Monitoring</h3>



<p class="wp-block-paragraph"><strong>Quick Health Check:</strong></p>



<pre class="wp-block-code"><code># Check all containers running
docker compose ps

# Check logs for errors
docker compose logs --tail=100 | grep -i error

# Check disk space
df -h</code></pre>



<h3 class="wp-block-heading" id="weekly-tasks">Weekly Tasks</h3>



<ol class="wp-block-list">
<li><strong>Review WordPress updates</strong> &#8211; Check dashboard for plugin/theme updates</li>



<li><strong>Check SSL expiry</strong> &#8211; Certbot should auto-renew, but verify</li>



<li><strong>Review access logs</strong> &#8211; Check for unusual activity</li>



<li><strong>Database backup</strong> &#8211; Automated with weekly backup system</li>
</ol>



<h3 class="wp-block-heading" id="monthly-tasks">Monthly Tasks</h3>



<ol class="wp-block-list">
<li><strong>Update Docker images</strong> &#8211; Pull latest stable versions</li>



<li><strong>Security scan</strong> &#8211; Run Wordfence deep scan</li>



<li><strong>Performance review</strong> &#8211; Check page load times</li>



<li><strong>Backup verification</strong> &#8211; Test restore from backup</li>
</ol>



<h2 class="wp-block-heading" id="lessons-learned">Lessons Learned</h2>



<h3 class="wp-block-heading" id="what-went-well">What Went Well</h3>



<ol class="wp-block-list">
<li><strong>Docker Compose</strong>: Made the entire stack manageable and reproducible</li>



<li><strong>Separation of Concerns</strong>: Each service in its own container simplified troubleshooting</li>



<li><strong>Environment Variables</strong>: Kept secrets out of version control</li>



<li><strong>Health Checks</strong>: Prevented startup race conditions</li>



<li><strong>Documentation</strong>: Writing everything down saved hours later</li>
</ol>



<h3 class="wp-block-heading" id="what-i-d-do-differently">What I&#8217;d Do Differently</h3>



<ol class="wp-block-list">
<li><strong>Start with Backups</strong>: I added automated backups later; should have been day one</li>



<li><strong>Monitoring Earlier</strong>: Wished I&#8217;d set up monitoring before going live</li>



<li><strong>Staging Environment</strong>: Would have been useful for testing updates</li>



<li><strong>Better Logging</strong>: Should have configured centralized logging from the start</li>
</ol>



<h3 class="wp-block-heading" id="unexpected-challenges">Unexpected Challenges</h3>



<ol class="wp-block-list">
<li><strong>FastCGI Configuration</strong>: Took several iterations to get right</li>



<li><strong>Volume Permissions</strong>: File ownership between containers required attention</li>



<li><strong>Certificate Initial Setup</strong>: The chicken-and-egg problem with SSL</li>



<li><strong>Network Between Containers</strong>: Understanding Docker networking took time</li>
</ol>



<h2 class="wp-block-heading" id="the-results">The Results</h2>



<p class="wp-block-paragraph">After several days of configuration, testing, and optimization:</p>



<p class="wp-block-paragraph"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Performance:</strong></p>



<ul class="wp-block-list">
<li>Page load time: &lt; 1 second</li>



<li>99.9% uptime since launch</li>



<li>A+ SSL rating</li>
</ul>



<p class="wp-block-paragraph"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Security:</strong></p>



<ul class="wp-block-list">
<li>Strong TLS configuration</li>



<li>Firewall configured</li>



<li>Security plugins active</li>



<li>Regular updates</li>
</ul>



<p class="wp-block-paragraph"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Maintainability:</strong></p>



<ul class="wp-block-list">
<li>Single docker-compose command to update</li>



<li>Automated SSL renewal</li>



<li>Automated backups (weekly)</li>



<li>Comprehensive documentation</li>
</ul>



<h2 class="wp-block-heading" id="what-s-next-">What&#8217;s Next?</h2>



<p class="wp-block-paragraph">With the web server foundation solid, the next challenge was email. In <strong>Part 2</strong>, I&#8217;ll cover:</p>



<ul class="wp-block-list">
<li>Setting up a full-featured mail server with docker-mailserver</li>



<li>Configuring SMTP, IMAP, DKIM, SPF, and DMARC</li>



<li>Integrating PostfixAdmin for email account management</li>



<li>Troubleshooting network and authentication issues</li>



<li>Dealing with Fail2Ban blocking legitimate traffic</li>



<li>The satisfying moment when the first email arrives</li>
</ul>



<h2 class="wp-block-heading" id="resources-references">Resources &amp; References</h2>



<p class="wp-block-paragraph"><strong>Official Documentation:</strong></p>



<ul class="wp-block-list">
<li><a href="https://docs.docker.com/compose/">Docker Compose Documentation</a></li>



<li><a href="https://hub.docker.com/_/wordpress">WordPress Docker Image</a></li>



<li><a href="https://nginx.org/en/docs/">Nginx Documentation</a></li>



<li><a href="https://letsencrypt.org/docs/">Let&#8217;s Encrypt Documentation</a></li>
</ul>



<p class="wp-block-paragraph"><strong>Helpful Tools:</strong></p>



<ul class="wp-block-list">
<li><a href="https://www.ssllabs.com/ssltest/">SSL Labs</a> &#8211; Test SSL configuration</li>



<li><a href="https://gtmetrix.com/">GTmetrix</a> &#8211; Performance testing</li>



<li><a href="https://hub.docker.com/">Docker Hub</a> &#8211; Container images</li>
</ul>



<p class="wp-block-paragraph"><strong>My Repository:</strong></p>



<ul class="wp-block-list">
<li>Full <code>docker-compose.yml</code> and configurations available</li>



<li>Detailed documentation in <code>docs/</code> directory</li>



<li>Backup and restore scripts included</li>
</ul>



<h2 class="wp-block-heading" id="conclusion">Conclusion</h2>



<p class="wp-block-paragraph">Building a self-hosted WordPress website with Docker was both challenging and incredibly rewarding. I now have:</p>



<ul class="wp-block-list">
<li><strong>Complete control</strong> over my web presence</li>



<li><strong>Deep understanding</strong> of how modern web infrastructure works</li>



<li><strong>Scalable foundation</strong> for adding more services (like email!)</li>



<li><strong>Skills</strong> transferable to professional DevOps work</li>
</ul>



<p class="wp-block-paragraph">The best part? Everything is reproducible. If my server dies tomorrow, I can rebuild the entire stack from my docker-compose.yml and restore from backups.</p>



<p class="wp-block-paragraph"><strong>Total time invested:</strong> ~3 days for initial setup + ongoing maintenance</p>



<p class="wp-block-paragraph"><strong>Worth it?</strong> Absolutely. The learning experience alone was invaluable, and having complete ownership of my data is priceless.</p>



<p class="wp-block-paragraph">In the next post, I&#8217;ll dive into the real challenge: building a production-ready mail server. Email is notoriously difficult to self-host, and I learned that the hard way&#8230;</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph"><strong>Stay tuned for Part 2: Building the Mail Server &#8211; DKIM, Dovecot, and Debugging</strong></p>



<p class="wp-block-paragraph"><em>Questions or feedback? Feel free to reach out!</em></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph"><strong>Published:</strong> November 2025<br><strong>Author:</strong> Frank @ BlueElysium<br><strong>Series:</strong> Building a Self-Hosted Web &amp; Mail Server<br><strong>Part:</strong> 1 of 2</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blueelysium.net/2025/11/23/building-blueelysium-part-1-the-web-server-foundation/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Self Hosting Nightmare</title>
		<link>https://blueelysium.net/2025/11/07/self-hosting-nightmare/</link>
					<comments>https://blueelysium.net/2025/11/07/self-hosting-nightmare/#respond</comments>
		
		<dc:creator><![CDATA[ffortunato]]></dc:creator>
		<pubDate>Fri, 07 Nov 2025 19:37:32 +0000</pubDate>
				<category><![CDATA[Web Site Management]]></category>
		<guid isPermaLink="false">https://blueelysium.net/?p=891</guid>

					<description><![CDATA[So there was a power failure at my home (where this beautiful site is hosted) and when the power came back on this website wouldn&#8217;t come up. I scoured through nginx configurations and even asked copilot and cursor to help me out with some AI trouble shooting. Where did it turn out the error was? [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">So there was a power failure at my home (where this beautiful site is hosted) and when the power came back on this website wouldn&#8217;t come up. I scoured through nginx configurations and even asked copilot and cursor to help me out with some AI trouble shooting.</p>



<p class="wp-block-paragraph">Where did it turn out the error was? The IP pass through between my AT&amp;T Gateway and my home ubiquity router!</p>



<p class="wp-block-paragraph">The Pass through on the AT&amp;T gateway wasn&#8217;t providing the external IP address to my internal router. Typically others suggest using dhcp setting on both the AT&amp;T modem and the Ubiquity router but I was forced to used fixed IP addresses because the Ubiquity router kept getting an IP from the AT&amp;T VLAN.  Actual details to follow.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blueelysium.net/2025/11/07/self-hosting-nightmare/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>CVBA Ocean Beach Men&#8217;s A</title>
		<link>https://blueelysium.net/2024/08/11/cvba-ocean-beach-mens-a/</link>
					<comments>https://blueelysium.net/2024/08/11/cvba-ocean-beach-mens-a/#comments</comments>
		
		<dc:creator><![CDATA[ffortunato]]></dc:creator>
		<pubDate>Sun, 11 Aug 2024 03:31:27 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://blueelysium.net/?p=784</guid>

					<description><![CDATA[Good time playing in the Men&#8217;s A this weekend in Ocean Beach. Had a great partner in Jordan Morgan (who also brough an amazing cheering gallery).]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Good time playing in the Men&#8217;s A this weekend in Ocean Beach. Had a great partner in Jordan Morgan (who also brough an amazing cheering gallery).</p>



 [<a href="https://blueelysium.net/2024/08/11/cvba-ocean-beach-mens-a/">See image gallery at blueelysium.net</a>] 
]]></content:encoded>
					
					<wfw:commentRss>https://blueelysium.net/2024/08/11/cvba-ocean-beach-mens-a/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>Family Reunion</title>
		<link>https://blueelysium.net/2024/05/13/family-reunion/</link>
					<comments>https://blueelysium.net/2024/05/13/family-reunion/#respond</comments>
		
		<dc:creator><![CDATA[ffortunato]]></dc:creator>
		<pubDate>Mon, 13 May 2024 16:35:19 +0000</pubDate>
				<category><![CDATA[Family]]></category>
		<guid isPermaLink="false">https://blueelysium.net/?p=765</guid>

					<description><![CDATA[A few pictures from the reunion we had with the extended Evans family.]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">A few pictures from the reunion we had with the extended Evans family.</p>



 [<a href="https://blueelysium.net/2024/05/13/family-reunion/">See image gallery at blueelysium.net</a>] 
]]></content:encoded>
					
					<wfw:commentRss>https://blueelysium.net/2024/05/13/family-reunion/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Host Website in Containers</title>
		<link>https://blueelysium.net/2024/04/25/host-website-in-containers-2/</link>
					<comments>https://blueelysium.net/2024/04/25/host-website-in-containers-2/#respond</comments>
		
		<dc:creator><![CDATA[ffortunato]]></dc:creator>
		<pubDate>Thu, 25 Apr 2024 17:06:37 +0000</pubDate>
				<category><![CDATA[Web Site Management]]></category>
		<guid isPermaLink="false">https://blueelysium.net/?p=730</guid>

					<description><![CDATA[Part two: ISP, Modem / Router and Registration In part one we focused on building and installing the hardware and software needed to host a website. The article focuses on In a residential setting internet connections are provided by an ISP. The ISP provides a gateway / modem to connect to the internet that include [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Part two: ISP, Modem / Router and Registration</p>



<p class="wp-block-paragraph">In part one we focused on building and installing the hardware and software needed to host a website. The article focuses on </p>



<p class="wp-block-paragraph">In a residential setting internet connections are provided by an ISP. The ISP provides a gateway / modem to connect to the internet that include basic routing functions. Individuals may also install a router to implement more advanced routing rules and network management. The key to these components are to ensure the gateway forwards all traffic to the router and the router forward specific ports (80, http, 443, https) to the computer hosting the website. </p>



<p class="wp-block-paragraph">Make sure to add port forwarding rules so the router passes data on ports 80 and 443 to the computer that is hosting the website.</p>



<p class="wp-block-paragraph">ISPs may have some other gotchas that make hosting difficult. They may block specific ports. Normally, the block port 25 to prevent spam but other go so far as to block port 80 which will make web hosting really difficult. ISPs also normally assign IP addresses via DHCP. If the lease for DHCP is short it will make maintaining DNS really difficult.</p>



<p class="wp-block-paragraph">The good news as all the hurdles listed can be mitigated through a call to the ISP. </p>



<ol class="wp-block-list">
<li>DHCP renewing all the time? 
<ul class="wp-block-list">
<li>Look at the setting on the modem and increase the renewal period to the maximum value.</li>



<li>Purchase a Static IP Address for a few additional dollars a month.</li>
</ul>
</li>



<li>Port 80 is blocked? A call to the ISP tech support and they will open the port for you.</li>
</ol>



<p class="wp-block-paragraph">Every website needs a web address that is provisioned by a registrar. There are a solid list of them available and include names like Square Space and Go Daddy. Prices for web addresses vary. Most off brand names go for $20 per year. Choose a registrar enter some billing information and the domain is yours.</p>



<p class="wp-block-paragraph">Now the web address needs to be associated with the IP address you are hosting from and this is managed by DNS entries. Normally, the registrar provides an interface for managing these values. The basic entry needed for forwarding traffic to your address is: </p>



<figure class="wp-block-table"><table><tbody><tr><td>Host</td><td>Type</td><td>Priority</td><td>Data</td></tr><tr><td>@</td><td>A</td><td>N/A</td><td>&lt;IP Address></td></tr></tbody></table></figure>



<p class="wp-block-paragraph">In Part Three we will look at spinning up some containers.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blueelysium.net/2024/04/25/host-website-in-containers-2/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Host Website In Containers</title>
		<link>https://blueelysium.net/2024/04/25/host-website-in-containers/</link>
					<comments>https://blueelysium.net/2024/04/25/host-website-in-containers/#respond</comments>
		
		<dc:creator><![CDATA[ffortunato]]></dc:creator>
		<pubDate>Thu, 25 Apr 2024 05:19:29 +0000</pubDate>
				<category><![CDATA[Web Site Management]]></category>
		<guid isPermaLink="false">https://blueelysium.net/?p=724</guid>

					<description><![CDATA[Part one: The gear and the software Just finished hosting this web site utilizing containers to learn a bit more about docker and docker compose. There were several great sites to move me along my way and then the pure excitement of backing up the existing site and restoring it to the new system. The [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><strong>Part one: The gear and the software</strong></p>



<p class="wp-block-paragraph">Just finished hosting this web site utilizing containers to learn a bit more about docker and docker compose. There were several great sites to move me along my way and then the pure excitement of backing up the existing site and restoring it to the new system.</p>



<p class="wp-block-paragraph"><strong>The equipment</strong>: Built this little engine from scratch, my first build. Used the guts of an old system that I purchased years ago. Gutted the motherboard, cpu and ram while retaining the power supply, fans and hard drives.</p>



<figure class="wp-block-table"><table><tbody><tr><td><strong>Equipment</strong></td><td><strong>Description</strong></td></tr><tr><td>RAM</td><td><a href="https://www.amazon.com/gp/product/B07RW6Z692/ref=ppx_yo_dt_b_asin_title_o04_s00?ie=UTF8&amp;psc=1">Corsair VENGEANCE LPX DDR4 RAM 32GB (2x16GB) 3200MHz</a></td></tr><tr><td>CPU</td><td><a href="https://www.amazon.com/gp/product/B09NPJRDGD/ref=ppx_yo_dt_b_asin_title_o05_s00?ie=UTF8&amp;psc=1">Intel Core i5 Core 12400F Desktop Processor&nbsp;</a></td></tr><tr><td>Drives</td><td><a href="https://www.amazon.com/gp/product/B078DPCY3T/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&amp;psc=1">Samsung 860 EVO 1TB 2.5 Inch SATA III Internal SSD</a></td></tr><tr><td>Motherboard</td><td><a href="https://www.amazon.com/gp/product/B09PX326Q8/ref=ppx_yo_dt_b_asin_title_o06_s00?ie=UTF8&amp;psc=1">MSI PRO H610M-G DDR4 DDR4 Motherboard (mATX, 12th Gen Intel Core, LGA 1700 Socket</a></td></tr></tbody></table></figure>



<p class="wp-block-paragraph">Hardware</p>



<p class="wp-block-paragraph"><strong>The software:</strong>&nbsp;I had previously been hosting the system with the software listed below so why change what ain’t broke. Well except moving it to containers…</p>



<figure class="wp-block-table"><table><tbody><tr><td><strong>Service</strong></td><td><strong>Software</strong></td></tr><tr><td>Operating System</td><td>Ubuntu</td></tr><tr><td>Image Management</td><td>Docker</td></tr><tr><td>Web Server</td><td>nginx</td></tr><tr><td>Database</td><td>mysql</td></tr><tr><td>Content Management</td><td>wordpress</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">Now we are off to the races. The system is willing to turn on but the mother board is flashing indicators that something is wrong. Okay, needed to read though the motherboard specification to find that led 3 mean that the ram is bad. So powered down the system and more firmly inserted the ram. Bingo, we make it to the bios which in turn seems to recognize everything.</p>



<p class="wp-block-paragraph">Software</p>



<p class="wp-block-paragraph">Downloaded Ubuntu to a flash drive and created a USB bootable image. Pointed the bios to boot from USB and we are off to the races with the OS installation and creation of a user account. Now is a good time to propose getting a solid password management tool because by the end of this you will have accumulated several (check out&nbsp;<a href="https://keepassxc.org/">KeePassXC</a>).</p>



<p class="wp-block-paragraph">Time to install docker because it will help with the management of<br>* images – snapshot of the software to be run<br>* containers – running instance of the software<br>* volumes – ability for containers to access a centralized set of data<br>That will eventually host the web site.</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://blueelysium.net/2024/04/25/host-website-in-containers/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Remembering Dad</title>
		<link>https://blueelysium.net/2023/05/30/remembering-dad/</link>
					<comments>https://blueelysium.net/2023/05/30/remembering-dad/#respond</comments>
		
		<dc:creator><![CDATA[ffortunato]]></dc:creator>
		<pubDate>Tue, 30 May 2023 03:17:39 +0000</pubDate>
				<category><![CDATA[Family]]></category>
		<guid isPermaLink="false">https://blueelysium.net/?p=702</guid>

					<description><![CDATA[]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-video"><video height="720" style="aspect-ratio: 1280 / 720;" width="1280" controls src="https://blueelysium.net/wp-content/uploads/2023/05/John_F._Fortunato.mp4"></video></figure>



 [<a href="https://blueelysium.net/2023/05/30/remembering-dad/">See image gallery at blueelysium.net</a>] 
]]></content:encoded>
					
					<wfw:commentRss>https://blueelysium.net/2023/05/30/remembering-dad/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		<enclosure url="https://blueelysium.net/wp-content/uploads/2023/05/John_F._Fortunato.mp4" length="174109503" type="video/mp4" />

			</item>
		<item>
		<title>Midjourney v5</title>
		<link>https://blueelysium.net/2023/03/17/midjourney-v5/</link>
					<comments>https://blueelysium.net/2023/03/17/midjourney-v5/#respond</comments>
		
		<dc:creator><![CDATA[ffortunato]]></dc:creator>
		<pubDate>Fri, 17 Mar 2023 22:50:41 +0000</pubDate>
				<category><![CDATA[Discovery]]></category>
		<guid isPermaLink="false">https://blueelysium.net/?p=627</guid>

					<description><![CDATA[Had an absolute blast playing with Midjourney today. This is an AI software that lets you build images from a narrative you provide. The new web icon was produced by this software. See below. I feel this picture pretty well encompasses Blue Elysium. Give the software a try on Discord. You can also see the [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Had an absolute blast playing with Midjourney today. This is an AI software that lets you build images from a narrative you provide. The new web icon was produced by this software. See below.</p>



<figure class="wp-block-image size-full"><img fetchpriority="high" decoding="async" width="1024" height="1024" src="https://blueelysium.net/wp-content/uploads/2023/03/DataAlias_web_site_icon._blue_elysium._simple._fields._ethereal_03.png" alt="" class="wp-image-620" srcset="https://blueelysium.net/wp-content/uploads/2023/03/DataAlias_web_site_icon._blue_elysium._simple._fields._ethereal_03.png 1024w, https://blueelysium.net/wp-content/uploads/2023/03/DataAlias_web_site_icon._blue_elysium._simple._fields._ethereal_03-300x300.png 300w, https://blueelysium.net/wp-content/uploads/2023/03/DataAlias_web_site_icon._blue_elysium._simple._fields._ethereal_03-150x150.png 150w, https://blueelysium.net/wp-content/uploads/2023/03/DataAlias_web_site_icon._blue_elysium._simple._fields._ethereal_03-768x768.png 768w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">I feel this picture pretty well encompasses Blue Elysium. Give the software a try on <a href="https://docs.midjourney.com/docs/quick-start">Discord</a>. </p>



<p class="wp-block-paragraph">You can also see the work I did. Good, bad and really bad.</p>



 [<a href="https://blueelysium.net/2023/03/17/midjourney-v5/">See image gallery at blueelysium.net</a>] 



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://blueelysium.net/2023/03/17/midjourney-v5/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
