Practical DevSecOps: Deploying a Secure Blog Step by Step

MandlaMandla
9 min read

I haven’t posted in a while but today I decided to make it day one of changing this and go on a journey where we talk DevSecOps and other development practices. So, to all my DevSecOps enthusiasts and Pros working on something, let’s bring those discussions up and make DevSecOps more approachable. I am committing to sharing and growing in this ever evolving sphere. So, it’s not about just showing polished projects, but documenting the real steps, mistakes, fixes, and lessons I pick up along the way.

When most people talk about DevSecOps, it often sounds abstract, like a buzzword and high-level diagrams, or enterprise-scale case studies. But I wanted to start differently. I’m beginning with something small, personal, and practical: deploying a simple blog.
Why? Because I believe security shouldn’t only live in “big projects.” It should be a habit from the very first line of code, from the very first deployment.

Why Start with a Blog?

On the surface, deploying a blog looks too simple to matter. But that’s the point.

  • A blog still lives on a server.

  • It still faces the internet.

  • It still has an attack surface.

If we treat small deployment casually, we end up carrying that mindset into bigger, more complex systems. By starting small but treating it seriously, I’m continously training myself to think and prioritize good practices that all DevSecOps practitioners should.

The First Mindset Shift

Traditional DevOps often focuses on speed, automation, and efficiency. DevSecOps adds another layer of resilience and security, baked into the process itself.

For me, the first mental shift was simple but powerful:

Even a small project deserves enterprise-grade thinking.

That means asking questions like:

  • How do I handle secrets and environment variables?

  • How do I monitor logs for suspicious activity?

  • Am I patching dependencies?

  • What’s my rollback strategy if something breaks?

Even if the project is “just a blog,” practicing this discipline early makes it second nature later.

What This Series Will Cover

This blog is the first entry in an ongoing DevSecOps journal. Over time, I’ll document:

  • How I secure different stages of deployment.

  • Tools I try (and whether they’re worth keeping).

  • Mistakes I make (because they will be there) and how to work through them.

  • Lessons that can scale from small side projects to enterprise apps.

Takeaway #1

Security isn’t about project size, it’s about mindset. Start small but start secure.

Takeaway #2

Treat your personal projects as testbeds for professional practices. They’re safe places to experiment, fail, and refine your approach.

Takeaway #3

DevSecOps isn’t a destination, it’s a habit that grows stronger with repetition.

Looking Ahead

This post is a long one because it sets the stage. The upcoming entries will be shorter (I am tempted to promise but will hold it for now, although I can say, they won’t be this long) they’ll be more focused, each highlighting a specific step or tool.

If you’re also walking the DevSecOps path, or even just curious, I’d love for you to follow along, share your thoughts, and compare notes.

What’s the first security step you take when deploying an app?

Let’s trade ideas.

Step 1: Setting Up the Repository

Everything starts with a home for the project. That meant creating a GitHub repository (if you don’t have a GitHub account, go ahead and create one...its free)

  • Give your repo a clear name, I named this one, secure-blog-deployment.

  • I made it public, because sharing is part of the journey and also a way to showcase your work.

  • Add a README right away so anyone landing on the repo knows what it’s about. This is a personal choice for me, and I find it to be good practice.

This feels basic right, but it’s the foundation of collaboration and transparency and that’s two things DevSecOps is built on.
By the way, I would suggest you also have or signup with a cloud provider and setup your account, AWS, Azure, your choice. Although at this stage we are still setting up and not there yet in terms of utilization, the cloud is our playground, and we’ll be needing the environment pretty soon.

Step 2: Connecting Local Work to GitHub

Next, comes the linking of the local machine to GitHub so as to push changes. So, let’s ensure you have npm, node.js and git up and running, to check if all is well and available, run the following commands...
node --version

npm --version

git --version

💡
At this stage, I was running the project as root. While this works for local development, it’s not a best practice for production. Running as root increases risk if the system is compromised. In future iterations, I’ll switch to a dedicated non-root user for running the application and containers.

  • Create a local project folder on computer.

  • Initialize it with git init.

  • Connect it to GitHub with git remote add origin.

  • Pull down the README to sync things up.

Now the local environment and GitHub repo are in sync and can commit and push as we build.

Step 3: Building the Application

With the repo ready, it’s time to create something to deploy. In this case I’ve went with Flask in Python because it’s lightweight and perfect for small projects.

Here’s what I did:

  1. Create a project structure with a templates folder for HTML.

  2. Set up a virtual environment so dependencies are isolated.

In Python development, a virtual environment is like a self-contained workspace for your project. It keeps all the packages your project needs in one isolated folder, so you don’t risk conflicts with other projects or cluttering your system Python. Each project can have its own versions of libraries without interfering with others. Creating one is easy:

python3 -m venv venv
source venv/bin/activate

You'll know it worked when you see (venv) appear at the beginning of your terminal prompt.

Once activated, any packages you install with pip stay inside that environment. To share your setup, you can export a requirements.txt file so others can recreate the same environment on their machine. This keeps projects clean, predictable, and fully reproducible and this is a must-have habit for professional Python development.

  1. Installed Flask (pip install Flask).

  2. In VS Code, I wrote a small Flask app (app.py) that returns a simple home page.

  3. Adding home.html inside the templates folder.

When I ran flask run, I could open the app in my browser and see “Welcome to My Blog!”. A small step, but an important milestone.

This isn't an error; it's a warning, and it's a reminder of a crucial lesson about the difference between a development environment and a production environment. This is a critical reminder that a development server is for coding, not for real users. A robust, secure application requires a production-grade server.

Since the debugger is shown as off, you may also run into this issue just as I did, the python file was written with the debugger set to True, and when I came across this issue, I solved it with an explicit activation of the debugger with,

export FLASK_APP=app.py
export FLASK_DEBUG=1

Before you push your code to GitHub and commit. Remember to instruct gitignore for your virtual environment with,
echo "venv/" > .gitignore

You might have seen this file in other repositories, but do you know what it does?

A .gitignore file tells Git to ignore certain files and folders that you don't want to track in your repository. This prevents security risks and it's a key first line of defense in DevSecOps. By adding certain files to .gitignore, you can prevent sensitive information like temporary credentials, API keys, or configuration files from accidentally being pushed to a public repository.

Getting this right from the start is a simple but powerful habit that shows you're thinking about clean code and security from day one.

Takeaway: Your repo is your project’s foundation. Secure it early with a .gitignore and a clear README.

What other files do you always add to your .gitignore? Share in the comments!

Step 4: Automating with GitHub Actions

Now the DevOps part kicks in. I didn’t want this to be a manual “run it when I remember” project. I wanted automation and following these steps gets us there...

So, I set up a CI pipeline with GitHub Actions:

  • Created a .github/workflows/ci.yml file.

  • Defined a simple pipeline that:

    • Runs whenever I push to the main branch.

    • Sets up Python.

    • Installs Flask.

    • Runs a placeholder check (echo "Code check complete!").

It may look small, but this is powerful, every push now triggers a pipeline. Automation is one of the pillars of DevSecOps. This workflow will serve as a "safety net," automatically performing checks to ensure that the code is clean, formatted correctly, and free of basic errors.

Step 5: Adding Security With Snyk

Here’s where the “Sec” comes in....**
Static Application Security Testing (SAST)**. A SAST tool scans your code for potential vulnerabilities before it's ever deployed. At this point, we’ll add and use Snyk. Other options you may consider are Bandit, Semgrep for open source or go with SonarCloud for enterprise dashboards. What we want to do is to automatically scan for vulnerabilities in dependencies. So the purpose is to make sure your project’s code and dependencies are secure from the ground up, and that this security check happens automatically on GitHub’s servers as part of your CI pipeline.

I wanted my pipeline to do more than just build, so I added a security scan with Snyk:

  • Create a free Snyk account if you dont have one so that you get an API token.

  • Store the token in GitHub Secrets (because hardcoding secrets is a no-go).

  • Add a Snyk step in my pipeline to check for vulnerabilities in dependencies.

  • Generate a requirements.txt with pip freeze > requirements.txt.

Now, every time I push code, my pipeline runs and Snyk scans for vulnerabilities automatically. That’s DevSecOps in action, security built into the workflow, not bolted on later.

The first run I made with the above automation initially gave an error, I had to troubleshoot and eventually updated

uses: snyk/actions/python-3-11@master

to a more stable version

uses: snyk/actions/python@master

this is some real world scenarios which make DevSec fun, consistently adapting to changes in the tools and platforms.

Below is the screenshot showing the workflow scan that may look like a failure because of the red X, but you’ll have just ran your very first successful automated security scan. After resolving the path, the red X is not an error; it's Snyk doing its job by failing the build because it found vulnerabilities, one of which was the use of flask that in turn required another package which was a few versions behind. This from Snyk is a critical security practice known as "fail-fast." The goal is to prevent any code with known security risks from being deployed.

And after the update, that’s when I got that glorious green check mark, meaning my code is now safe to deploy.

DevSecOps is about continuous improvement, and I’m documenting each layer as I build it.

In the next post, I’ll go deeper into expanding the pipeline with more checks and eventually moving into deployment.

Takeaway: Start small, but start. Every project can be made more secure one step at a time.

0
Subscribe to my newsletter

Read articles from Mandla directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Mandla
Mandla

I’m a developer with a passion for building intuitive, secure, and high-performing web applications. I write about my journey through development, DevSecOps, and hands-on projects, sharing practical insights and lessons I learn along the way.