Issue 56 - A Docker Alternative, and Why You Must Verify Your Content
Dive into the importance of technical reviews to prevent broken tutorials, and explore Podman as a Docker alternative.
Issue 56
A broken step in a tutorial costs more than one frustrated reader. In this issue, you'll explore how working code turns into a broken draft, and how a full technical test protects your readers and the trust they place in you. And then you'll explore Podman, an alternative to Docker, and use it to set up a way to quickly test PHP locally.
Don't Skip the Technical Review
One of the worst things you can do when creating content for learners is to leave them stranded.
You've probably experienced this as a learner. You've followed a tutorial and gotten stuck along the way. You followed every step, copied the code and commands right from the page, but somewhere in the middle a command failed, or the code referenced a file you never created. Then you spent time looking for the step you must have skipped. And you probably thought it was your fault, until you discovered there was something missing.
The author probably wasn't lazy. They probably tested their code; it probably even came from a working example. But they likely never tested the final draft itself. The draft is what your reader holds.
You have to incorporate your own final walkthrough of your technical content, lessons, and more. And it starts with understanding how mistakes creep in.
How working code becomes a broken tutorial
You started with a working example. You ran it, it worked, and then you started writing. But as you write and revise, things shift, and it all starts with the example itself and how you sequence it.
You pull the code apart to tell the story
A tutorial walks the reader through building a solution piece by piece, so writing one means you have to take your finished code and split it into pieces you then arrange into a narrative. Every time you do that, you increase the chances you'll lose something important. An import gets removed in a fragment you cut. A configuration step lands after the command that needs it. A code block references a file the reader hasn't created yet, because you moved the step that creates it to a later section of the tutorial.
You won't notice any of this while writing. The code still works in your project. But the tutorial is broken, and you find out when a reader tells you.
You should use tools to pull code into your drafts from a working repository instead of pasting snippets in by hand. A build process that imports code from source files ensures they can't drift from code you can test outside of the draft. Issue 23 covered SnipSync, a tool built for this job.
But sourcing code only guarantees the code works, not how you sequenced the steps or explained it. The reader follows your learning pathway, with your prose between the fragments. Correct code in the wrong sequence still strands the reader on step six. The repository proves the code works, but it can't prove the draft does.
Editorial feedback moves things again
Once you get feedback from an editor or a reviewer, you change things again. You merge sections, split others apart, and move sections around. For example, a reviewer suggests introducing the database before the routes, so you swap the sections. The piece flows better now, but the swap moved a snippet ahead of the setup it depends on, and nobody caught it because the review focused on the words. And worse, you may have forgotten to move the transitions, so they're more confusing because they reference a section that doesn't exist where it used to.
Each feedback round takes you further from the version you originally ran. By the final draft, the sequence you tested and the sequence you're publishing may be considerably different/
A copy edit pass won't catch those issues
Most content review looks at sentences and paragraphs for spelling and style, clarity and tone. Those reviews are important for clarity, but they won't find a step that's out of order, because sequence bugs don't live in sentences. They live in the story.
And you still tell stories in technical content. A tutorial's story is "do this, then this, and here's why, and now you have a working thing." When you miss a step, the story has a plot hole, and the only way to find a plot hole is to experience the story from the beginning, the way the reader will. This is what a good developmental editor does. They ensure that the story is in place, and that one thing flows to the next. However, not every developmental editor is going to catch technical inconsistencies. Your readers will, though.
The fix is for you to walk the entire article from the top, like your readers will.
Run a real tech test
A tech test is a full content audit. You're auditing whether a reader can follow the prose and the code, together, from the first sentence to the final working result. You can automate running the commands, and you can hand your snippets to an AI to execute. Both will catch code that fails. Neither reads the piece as a reader. Neither notices the unexplained code block or the prerequisite you never mentioned. That's your job, because nobody knows the expected outcome like you.
Here's how to run one:
-
Start from a clean environment. Your machine has every dependency installed, every environment variable set, and every credential cached, so you're starting with a lot of assumptions. Use a fresh virtual machine, a container, or a new user account. A clean directory and a clean shell are the minimum.
-
Follow the draft from the top, copying from the draft. Not from your project. Run each command and add each code block from the document itself, in the order the document presents them. The moment you grab code from your repository instead, you're testing the wrong thing. Even if you give the readers full access to the finished example, you want to reduce any friction.
- Audit the prose as you go. Confirm every code block gets an introduction and an explanation, the way the Code Sandwich prescribes. Confirm your "signposts" along the way guide your reader through the story. Make sure each section picks up where the last one ended. Then confirm the prerequisites match what your clean environment actually needed. If you had to install something the prerequisites never mentioned, the prerequisites section is wrong.
- Test again after you reorganize the content. If you move a section, or delete a paragraph that contained a step, you have to test again. If the structure changed, the test you ran before the change no longer matters.
- Hand it to a second tester. You know what the missing step was supposed to say, so your brain supplies it without asking. A second person won't have any of that context. Where they get stuck is where your draft is broken.
As you review, you might find new things to explain, like missing steps or errors the reader might see. But if you catch yourself writing "if you see this error, try this instead," pause. Sometimes that note belongs there. But often it's a patch over a step you might have left out. Add the missing step and the note becomes unnecessary.
Your tech test has an expiration date
Passing a tech test means the tutorial worked on that day, in that environment, with those software versions. Dependencies ship new releases and APIs change underneath you. The tutorial that worked in March can fail in September without you changing a thing.
Frequent updates can break things, too. When a reader reports a broken step, it's tempting to patch that one section and publish. Don't. The upstream change that broke one step probably touched others, and the reader who reported the bug stopped at the first failure. They can't tell you about the next one your tutorial still contains. A revision is a reorganization like any other, so you have to give it the same testing treatment.
You can build accountability for this into your publishing platform and process.
- Display a "last verified" date instead of "last updated." Updated tells the reader you touched the file. Verified tells them you followed it end to end and it worked. When you verify the content's correctness, update the date. Learners will appreciate your transparency.
- Generate reports of the content you haven't verified recently so you stay on top of stale pages. Flag any content when the verified date gets too old. Six months is a reasonable starting point, and you can adjust it to how fast your stack moves. More important and high-value content might need more frequent updates.
- Show readers a notice when a page is past its verification window. A public "this may be out of date" banner holds you accountable in a very public way. A reader might overlook a date, but a big red banner admitting you haven't looked at the content in a while really stands out.
A reader who hits a broken step assumes they made the mistake, not the author. They retrace their work and waste time hunting for an error they didn't make. When they finally realize the error is in your tutorial, they stop trusting the rest of what you wrote, and the next time they need help, they look somewhere else.
If they can't complete your tutorial as written, that's your mistake, not theirs. Hold yourself to that standard, because your readers do.
Run Containers with Podman instead of Docker
Podman is a container engine similar to Docker. It understands the same images and commands as Docker, but each container runs as a regular child process of your user. There's no background service to configure, manage, or secure.
In this tutorial, you'll install Podman and use it to build an Apache image with PHP support so you can test websites locally.
Install Podman
On Fedora, install Podman with dnf:
$ sudo dnf install podman
On Ubuntu or Debian, use apt:
$ sudo apt install podman
On macOS, install Podman with Homebrew:
$ brew install podman
Linux runs containers natively. On macOS, containers run inside a lightweight virtual machine that Podman manages for you. Create and start the virtual machine with the following commands:
$ podman machine init
$ podman machine start
Verify the installation on any platform:
$ podman --version
Podman displays the version number. Now you can test it out,
Build the image
You'll test out Podman by creating a container image with Apache and PHP, and use it as a local development environment.
Create a project directory and switch into it:
$ mkdir podman-demo
$ cd podman-demo
Now create a Dockerfile. Podman reads Dockerfiles without any changes. Add the following code to the file to configure the image:
FROM php:8.3-apache
RUN a2enmod rewrite headers expires
RUN sed -i '/<Directory \/var\/www\/>/,/<\/Directory>/ s/AllowOverride None/AllowOverride All/' /etc/apache2/apache2.conf
EXPOSE 80
CMD ["apache2-foreground"]
The FROM line uses the official PHP image, using the variant that bundles Apache with PHP. That base image uses Debian's Apache packaging, which gives you the a2enmod helper. The first RUN uses it to enable the rewrite, headers, and expires modules, which .htaccess files commonly rely on for redirects, custom headers, and caching rules. The .htaccess file lets you configure many features for your website project. You won't use an .htaccess file in this tutorial, but this example demonstrates how Podman understands the same Dockerfile directives as Docker.
The second RUN edits Apache's main configuration file, changing AllowOverride None to AllowOverride All inside the <Directory /var/www/> block. Without that change, Apache ignores .htaccess files entirely, so the modules you enabled would have nothing to respond to.
EXPOSE 80 documents that the server listens on port 80 inside the container. And CMD runs Apache in the foreground, because a container lives only as long as its main process. If Apache went to the background, the container would exit immediately.
Build the image and tag it with the following command:
$ podman build -t podman-demo .
Podman downloads the base image, applies your configuration changes, and reports the new image's ID when it finishes:
STEP 1/5: FROM php:8.3-apache
Resolved "php" as an alias (/etc/containers/registries.conf.d/000-shortnames.conf)
Trying to pull docker.io/library/php:8.3-apache...
Getting image source signatures
Copying blob sha256:e4315bdbbc2f739b25dd3dd053ed6c1a3f1cb2f7aa93a1b7eb878d068efa6026
...
Copying config sha256:ea298c5a424f1d55478b88d34f27719c067e6903ea8c2d446d8a822b1bd1fbce
Writing manifest to image destination
STEP 2/5: RUN a2enmod rewrite headers expires
Enabling module rewrite.
To activate the new configuration, you need to run:
service apache2 restart
--> 7e29e8e88c69
STEP 3/5: RUN sed -i '/<Directory \/var\/www\/>/,/<\/Directory>/ s/AllowOverride None/AllowOverride All/' /etc/apache2/apache2.conf
--> f77a6b78a660
STEP 4/5: EXPOSE 80
--> a9bab236545e
STEP 5/5: CMD ["apache2-foreground"]
COMMIT podman-demo
--> 2d7e52aad65c
Successfully tagged localhost/podman-demo:latest
2d7e52aad65c469f0db69d5af61a441b3625c9d81f3c31063d313f1ad45bd79e
Now you can create some content and test it out.
Test the container
Now run the container and mount your project directory into it:
$ podman run -d --rm \
-p 8080:80 \
-v "$(pwd)":/var/www/html:Z \
--name podman-demo \
podman-demo
The -d flag runs the container in the background, and the --rm flag removes the container when it stops. The -p 8080:80 flag maps your machine's port 8080 to the container's port 80, where Apache is listening.
The -v flag mounts your current directory to /var/www/html, which is the image's document root. This means Apache serves your files directly rather than serving a copy baked into the image. The :Z suffix relabels the directory for SELinux, which Fedora and RHEL require before a container can read your files. On systems without SELinux, Podman ignores it.
Visit http://localhost:8080 in your browser, and you'll see your "Hello from Podman" page. Then visit http://localhost:8080/info.php, and you'll see the PHP configuration report, which proves Apache is handing PHP files to the interpreter rather than serving them as plain text.
To prove the mount is live, change the text inside the <h1> tag, save the file, and refresh the browser. The change appears immediately. No rebuild, no restart, because the container reads your directory, not a snapshot of it.
You can see the containers that are running with the podman ps command:
$ podman ps
The output displays the container name, uptime, image hash, and status:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
46cacd9a04d1 localhost/podman-demo:latest apache2-foregroun... 43 seconds ago Up 43 seconds 0.0.0.0:8080->80/tcp podman-demo
When you're done, stop the container:
$ podman stop podman-demo
Thanks to --rm, stopping it also removes it, and the image stays behind for next time.
You can see the images downloaded with podman images:
$ podman images
The output shows your podman-demo image and the image you based it on:
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/podman-demo latest 2d7e52aad65c 4 minutes ago 525 MB
docker.io/library/php 8.3-apache ea298c5a424f 5 days ago 525 MB
You now have a disposable Apache and PHP server you can point at any directory, running without a daemon and without root. You can use this on other projects as well by changing the directory you mount.
Remove your image with podman rmi:
$ podman rmi podman-demo
Podman untags and removes the image
Untagged: localhost/podman-demo:latest
Deleted: 2d7e52aad65c469f0db69d5af61a441b3625c9d81f3c31063d313f1ad45bd79e
Deleted: a9bab236545ed302540e0e77dc5c8bae2fc0ab648547347ca2df348ebdd44624
Deleted: f77a6b78a660544c1846c0f2befb747a376f415d41d5338039e790582a9ae374
Deleted: 7e29e8e88c6999f71da1ee366b2de62824a49bac9a38315eadbb9887d069460f
Podman gives you the same features as Docker, but with a more secure model that doesn't require a background process. And there are additional features you can take advantage of, too. For example, Podman's pods let you group containers that work together, such as a web server and its database, under a single name. Look at the Podman documentation to discover more.
Things To Explore
- terminal-browser is a web browser that runs in your terminal.
- terminal-code brings Visual Studio Code to your terminal by using
code-serverandterminal-browsertogether.
Parting Thoughts
Here are a couple of things for you to think about before next month's issue:
- Pick your most recent published tutorial. Spin up a clean environment and follow it as written, from the top, copying from the page. When it passes, add a verified date.
- Pick a piece of content you haven't touched in a while and test it in a clean environment. What changed? How can you stay on top of this?
- Switch one of your local Docker-powered projects over to Podman for a week and see how it works for you.
As always, thanks for reading.
You just read issue #55 of Code, Content, and Career with Brian Hogan. You can also browse the full archives of this newsletter.