What started as a quick fix to switch from mail
to smtp
in a config file ended up becoming a five-day journey into secure, repeatable, real-world DevOps practices. This post outlines that journey โ the lessons learned, the tools explored, and why I'm actually glad I "took the long way around."
๐ The Starting Point: Just Change a Mail Protocol
I began with a self-hosted deployment of Easy!Appointments, trying to force it to use SMTP instead of the default mail
protocol.
Why?
The default mail
protocol relies on the underlying PHP mail()
function, which in many server environments:
- Isn't configured properly
- Can result in mail getting silently dropped or marked as spam
- Lacks support for authentication or encryption (TLS/SSL)
For reliability and control, I wanted the application to use SMTP with authentication.
๐ง My Initial Plan
The initial plan? Hardcode new settings into email.php
and config.php
, and copy them into the container during image build via Dockerfile:
COPY config.php /var/www/html/config.php
COPY email.php /var/www/html/application/config/email.php
It worked โ but it also meant:
- My database and SMTP credentials were now stored in the image
- And those files were also committed to my Git repo (ouch)
Yes, it was a private repo. But still โ I knew I was cutting corners.
I quickly realised: this is not sustainable, nor secure.
๐ From Hardcoded to Runtime Environment
This led me to re-architect the deployment using environment variables and a custom docker-entrypoint.sh
script.
At startup, this script would:
- Read values from a
.env
file - Dynamically generate
config.php
andemail.php
Now I could inject credentials securely at runtime, without rebuilding the image or leaking secrets into version control.
cat <<EOF > /var/www/html/config.php
<?php
class Config {
const BASE_URL = "${BASE_URL}";
const DB_HOST = "${DB_HOST}";
...
}
EOF
๐ฆ Building My First Custom Image
This project was also my first real experience creating and publishing a custom Docker image. I started by hosting my code and Dockerfile in a private self-hosted Gitea instance, where I could iterate and test locally.
Once I had the docker-entrypoint.sh
working to generate the dynamic configs, I needed to make the image available for deployment. This is where I branched out into using GitHub Container Registry (GHCR) โ another first.
I created a GitHub repository specifically for the container image, added a GitHub Actions workflow to build and push it to GHCR, and ensured I could pull the tagged images securely from my deployment environment.
This involved:
- Creating and managing a GitHub personal access token (PAT) with correct scopes
- Debugging image visibility and authentication issues
- Learning how to tag and version releases clearly for future deployments
It turned into a real hands-on introduction to CI/CD workflows, container registries, and environment-secure image publishing.
๐ณ Docker, GHCR, and Git
Along the way, I:
- Rebuilt the Docker image multiple times to include the new script
- Learned to tag and push images to GitHub Container Registry (GHCR)
- Set up GitHub Actions to automate the build process
- Had to reset and reconnect local Git branches and force-push history when needed
This wasn't just tweaking config anymore โ it was full-cycle DevOps:
- Build โ Push โ Deploy โ Debug โ Document
๐ง What I Learned
- How
.env
files and entrypoint scripts can securely inject secrets at runtime - Why
const
vspublic static
matters in PHP apps (especially legacy ones) - How GHCR handles authentication, permissions, and visibility
- That Portainer has limitations for
.env
usage unless you're uploading manually - How to write proper release notes and version image tags
- How to manage a custom image repository from development through to production deployment
๐ ๏ธ Real-World Outcome
By the end of the journey, I had:
- A secure, repeatable containerized deployment
- Full SMTP support with user-defined variables
- Clean, versioned builds pushed to GHCR
- Configs generated dynamically at runtime
- A better understanding of Git/GHCR authentication flows
- My first fully custom-built and published Docker image
๐ญ Reflection: Was It Overkill?
I asked myself this several times. All I wanted to do was change mail
to smtp
.
But that friction led to:
- Understanding how to do it the right way
- A scalable pattern I can now apply to other apps
- A deep dive into config management, secrets, and Docker best practices
Using ChatGPT throughout felt like having a senior engineer pair-programming with me โ not cheating. I still had to troubleshoot, rebuild, and think critically at each stage.
๐ฎ Whatโs Next
- Expand this pattern into more services I self-host
- Explore Gitea webhooks or CI pipelines for self-hosted automation
- Keep tracking my learning on opensourceitsolutions.co.uk via Ghost
- Apply this all to my AZ-400 DevOps certification study path
If you've ever found yourself solving one small problem and ending up with a full-on infrastructure revamp โ you're not alone.
And if you havenโt yet? Just wait. ๐
๐ What This Deployment Was For
A big thank you to Alex Tselegidis, the creator of Easy!Appointments, for building such a flexible and self-hostable appointment booking solution.
This entire deployment journey wasnโt just for fun โ Iโve implemented Easy!Appointments as part of my own startup, Open Source IT Solutions.
We now use it to offer free 30-minute consultation sessions where we help individuals and businesses explore how open source software can simplify, secure, or scale their day-to-day operations.
Whether youโre a new startup or an established business looking to migrate from expensive proprietary systems, Iโd love to chat.
Book a free session here โ bookings.opensourceitsolutions.co.uk
I also maintain a growing list of open source software I recommend and implement.
Feel free to reach out if youโre curious how this can fit your business.
This hands-on deployment also tied in perfectly with my AZ-400 certification journey, which I recently started. Working on a real-world implementation like this โ rather than following only the static examples from Microsoft Learn โ helped me absorb the concepts more deeply. It's been a winโwin: practical experience that aligns directly with my certification goals.