<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Selinux on Fabio Lopes</title><link>https://fabiolopes.page/tags/selinux/</link><description>Recent content in Selinux on Fabio Lopes</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><copyright>Fabio M. Lopes</copyright><lastBuildDate>Mon, 09 Nov 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://fabiolopes.page/tags/selinux/index.xml" rel="self" type="application/rss+xml"/><item><title>Making EC2 Provisioning More Predictable Without Disabling SELinux</title><link>https://fabiolopes.page/p/making-ec2-provisioning-more-predictable-without-disabling-selinux/</link><pubDate>Mon, 09 Nov 2026 00:00:00 +0000</pubDate><guid>https://fabiolopes.page/p/making-ec2-provisioning-more-predictable-without-disabling-selinux/</guid><description>&lt;img src="https://fabiolopes.page/p/making-ec2-provisioning-more-predictable-without-disabling-selinux/banner_hu_647bf06009666136.png" alt="Featured image of post Making EC2 Provisioning More Predictable Without Disabling SELinux" /&gt;&lt;h1 id="rethinking-ec2-provisioning-package-versions-golden-images-and-selinux"&gt;&lt;a href="#rethinking-ec2-provisioning-package-versions-golden-images-and-selinux" class="header-anchor"&gt;&lt;/a&gt;Rethinking EC2 Provisioning: Package Versions, Golden Images, and SELinux&#10;&lt;/h1&gt;&lt;p&gt;I recently spent some time revisiting how we provision EC2 instances for a Laravel application running on AlmaLinux 9, Nginx, and PHP-FPM.&lt;/p&gt;&#10;&lt;p&gt;The initial goal was fairly straightforward: simplify an existing provisioning script by moving stable system configurations into a preconfigured machine image, while retaining some flexibility to manage runtime versions when new instances start.&lt;/p&gt;&#10;&lt;p&gt;As I worked through the scripts, though, I found a few things that deserved more attention than I had expected. Package versioning turned out to be more complicated than setting a few variables. Composer behaved differently when running without a conventional user environment. And enabling SELinux in Enforcing mode revealed a file-labeling issue that could affect every new application deployment.&lt;/p&gt;&#10;&lt;p&gt;Each problem was manageable on its own, but together they raised a broader question: how much work should an EC2 instance really be doing when it starts?&lt;/p&gt;&#10;&lt;h2 id="what-belongs-in-the-image-and-what-belongs-at-boot"&gt;&lt;a href="#what-belongs-in-the-image-and-what-belongs-at-boot" class="header-anchor"&gt;&lt;/a&gt;What Belongs in the Image, and What Belongs at Boot?&#10;&lt;/h2&gt;&lt;p&gt;The original provisioning script handled almost everything from scratch. It configured repositories, installed packages, created users and directories, set up PHP-FPM and Nginx, installed systemd services, and applied firewall and SELinux settings.&lt;/p&gt;&#10;&lt;p&gt;There&amp;rsquo;s a certain convenience to this approach, especially when you&amp;rsquo;re building an environment for the first time. Everything is in one place, and you can follow the entire setup process from beginning to end.&lt;/p&gt;&#10;&lt;p&gt;But I suggested to the team that we reconsider how much of that work needed to happen every time Auto Scaling launched a new instance.&lt;/p&gt;&#10;&lt;p&gt;Most of those configurations were already known and rarely changed. Repeating them at every startup added unnecessary work and introduced dependencies that could prevent an otherwise healthy instance from coming online.&lt;/p&gt;&#10;&lt;p&gt;My proposal was to move the stable parts into the machine image:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Nginx and PHP-FPM configuration.&lt;/li&gt;&#10;&lt;li&gt;Application directories and ownership.&lt;/li&gt;&#10;&lt;li&gt;Systemd units for the Laravel scheduler and Horizon.&lt;/li&gt;&#10;&lt;li&gt;Deployment-user permissions.&lt;/li&gt;&#10;&lt;li&gt;Persistent SELinux configuration.&lt;/li&gt;&#10;&lt;li&gt;Common operating-system settings.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;The initialization script would then focus on runtime checks, selected configuration adjustments, and starting the application services.&lt;/p&gt;&#10;&lt;p&gt;During the transition, I kept the original provisioning commands commented out. That made it easier to review the changes with the team and explain which responsibilities had moved into the image. Once the new process is fully validated, those obsolete blocks can be removed.&lt;/p&gt;&#10;&lt;h3 id="the-image-and-the-initialization-script-need-to-agree"&gt;&lt;a href="#the-image-and-the-initialization-script-need-to-agree" class="header-anchor"&gt;&lt;/a&gt;The image and the initialization script need to agree&#10;&lt;/h3&gt;&lt;p&gt;One thing became clear while making these changes: moving a command out of the provisioning script doesn&amp;rsquo;t make its responsibility disappear.&lt;/p&gt;&#10;&lt;p&gt;If the script no longer creates a directory, that directory must already exist in the image. The same applies to systemd units, permissions, and SELinux policies.&lt;/p&gt;&#10;&lt;p&gt;This means the machine image and its initialization script effectively become two parts of the same deployment contract.&lt;/p&gt;&#10;&lt;p&gt;They need to be versioned and tested together, even if they&amp;rsquo;re maintained separately. Otherwise, a perfectly valid change to one could break the other.&lt;/p&gt;&#10;&lt;h2 id="package-versioning-wasnt-as-straightforward-as-i-expected"&gt;&lt;a href="#package-versioning-wasnt-as-straightforward-as-i-expected" class="header-anchor"&gt;&lt;/a&gt;Package Versioning Wasn&amp;rsquo;t as Straightforward as I Expected&#10;&lt;/h2&gt;&lt;p&gt;Another improvement I wanted to make was controlling package versions more explicitly.&lt;/p&gt;&#10;&lt;p&gt;The original script relied on unversioned &lt;code&gt;dnf install&lt;/code&gt; commands. In an Auto Scaling environment, that creates an interesting risk: an instance launched today might install different package versions from those running since last week.&lt;/p&gt;&#10;&lt;p&gt;For a fleet that&amp;rsquo;s supposed to behave consistently, that&amp;rsquo;s not ideal.&lt;/p&gt;&#10;&lt;p&gt;I introduced version variables for PHP, Nginx, Redis, ZIP, and Composer. PHP, for example, was targeted at version 8.4.27 from the Remi repository.&lt;/p&gt;&#10;&lt;p&gt;I also added a helper to compare the installed RPM version against the requested version and synchronize it when necessary.&lt;/p&gt;&#10;&lt;p&gt;The initial idea looked something like this:&lt;/p&gt;&#10;&lt;div class="code-example"&gt;&#10; &lt;div class="code-toolbar"&gt;&lt;span&gt;Source code&lt;/span&gt;&lt;span class="code-language"&gt;bash&lt;/span&gt;&lt;/div&gt;&#10; &lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;span class="lnt"&gt;3&#10;&lt;/span&gt;&lt;span class="lnt"&gt;4&#10;&lt;/span&gt;&lt;span class="lnt"&gt;5&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;installed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;rpm -q --qf &lt;span class="s1"&gt;&amp;#39;%{VERSION}-%{RELEASE}&amp;#39;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$installed&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; !&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$version&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; dnf -y distro-sync &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;version&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fi&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;The intention was to avoid unnecessary package operations when the correct version was already installed.&lt;/p&gt;&#10;&lt;p&gt;It made sense at first, but reviewing the implementation exposed a problem with my assumptions.&lt;/p&gt;&#10;&lt;p&gt;Not every package associated with PHP follows PHP&amp;rsquo;s version numbering.&lt;/p&gt;&#10;&lt;p&gt;For example, &lt;code&gt;php-pear&lt;/code&gt; has its own independent version. In this environment, &lt;code&gt;php-zip&lt;/code&gt; was a virtual package provided by &lt;code&gt;php-pecl-zip&lt;/code&gt;. Treating both as ordinary PHP RPMs with the same version string would lead to incorrect package resolution.&lt;/p&gt;&#10;&lt;p&gt;I also realized that synchronizing individual PHP packages wasn&amp;rsquo;t necessarily the safest approach. Extensions and runtime packages have dependencies on one another, and changing them separately can cause conflicts during a version transition.&lt;/p&gt;&#10;&lt;p&gt;A single DNF transaction that resolves the related packages together is generally a better approach.&lt;/p&gt;&#10;&lt;h3 id="version-pinning-is-only-part-of-the-solution"&gt;&lt;a href="#version-pinning-is-only-part-of-the-solution" class="header-anchor"&gt;&lt;/a&gt;Version pinning is only part of the solution&#10;&lt;/h3&gt;&lt;p&gt;There&amp;rsquo;s another limitation worth mentioning.&lt;/p&gt;&#10;&lt;p&gt;Even if we specify the exact package versions correctly, those packages still need to be available in the repository when a new instance starts.&lt;/p&gt;&#10;&lt;p&gt;If an older version gets removed, the installation may fail. The same problem can affect a rollback if the required packages are no longer published.&lt;/p&gt;&#10;&lt;p&gt;This creates a trade-off.&lt;/p&gt;&#10;&lt;p&gt;Keeping runtime version management in the initialization script gives us flexibility. We can change a PHP version without necessarily rebuilding the entire image.&lt;/p&gt;&#10;&lt;p&gt;But it also means that launching a new instance still depends on external repositories being available and serving the expected packages.&lt;/p&gt;&#10;&lt;p&gt;For production workloads, particularly those managed by Auto Scaling, that&amp;rsquo;s a dependency I&amp;rsquo;d rather minimize.&lt;/p&gt;&#10;&lt;p&gt;A controlled package repository could help. So could building and publishing new machine images whenever runtime versions change.&lt;/p&gt;&#10;&lt;p&gt;Neither approach is completely free of maintenance, but both provide more control over what actually gets deployed.&lt;/p&gt;&#10;&lt;h2 id="enabling-selinux-revealed-another-problem"&gt;&lt;a href="#enabling-selinux-revealed-another-problem" class="header-anchor"&gt;&lt;/a&gt;Enabling SELinux Revealed Another Problem&#10;&lt;/h2&gt;&lt;p&gt;The other major part of this work involved running the application with SELinux in Enforcing mode.&lt;/p&gt;&#10;&lt;p&gt;The Laravel application uses a release-based deployment structure:&lt;/p&gt;&#10;&lt;div class="code-example"&gt;&#10; &lt;div class="code-toolbar"&gt;&lt;span&gt;Source code&lt;/span&gt;&lt;span class="code-language"&gt;text&lt;/span&gt;&lt;/div&gt;&#10; &lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;span class="lnt"&gt;3&#10;&lt;/span&gt;&lt;span class="lnt"&gt;4&#10;&lt;/span&gt;&lt;span class="lnt"&gt;5&#10;&lt;/span&gt;&lt;span class="lnt"&gt;6&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/var/www/application/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── current -&amp;gt; releases/&amp;lt;release-id&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── releases/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── &amp;lt;release-id&amp;gt;/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── public/index.php&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── storage/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Each deployment prepares a new release directory and, once everything is ready, updates the &lt;code&gt;current&lt;/code&gt; symbolic link.&lt;/p&gt;&#10;&lt;p&gt;The application needs to read its code and write to specific locations, mainly &lt;code&gt;storage&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;From a traditional Linux permissions perspective, that&amp;rsquo;s relatively easy to manage through ownership and permission bits.&lt;/p&gt;&#10;&lt;p&gt;SELinux adds another layer: every file also has a security context that determines how processes can interact with it.&lt;/p&gt;&#10;&lt;h3 id="the-issue-was-with-file-labels"&gt;&lt;a href="#the-issue-was-with-file-labels" class="header-anchor"&gt;&lt;/a&gt;The issue was with file labels&#10;&lt;/h3&gt;&lt;p&gt;During deployment, application files were prepared or extracted under &lt;code&gt;/tmp&lt;/code&gt; before being moved into the release directory.&lt;/p&gt;&#10;&lt;p&gt;Some of those files retained the SELinux type from their original location: &lt;code&gt;tmp_t&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;That matters because moving a file doesn&amp;rsquo;t necessarily update its SELinux context to match the destination directory.&lt;/p&gt;&#10;&lt;p&gt;So even when the ownership and Unix permissions look correct, Nginx or PHP-FPM may still be unable to access the file.&lt;/p&gt;&#10;&lt;p&gt;The solution was to correct the labels rather than disable SELinux or switch it to Permissive mode.&lt;/p&gt;&#10;&lt;p&gt;For this application, the relevant contexts were:&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Content&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;SELinux type&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Purpose&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Application code&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;httpd_sys_content_t&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Read access&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Writable storage&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;httpd_sys_rw_content_t&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Authorized writes&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Release symlink&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Depends on path policy&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Points to the active release&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;During validation, the symbolic link appeared with &lt;code&gt;var_t&lt;/code&gt;. That&amp;rsquo;s an observation from this environment, not a general rule for how application files should be labeled.&lt;/p&gt;&#10;&lt;p&gt;What matters is having the appropriate persistent file-context rules and ensuring that deployed files actually receive those labels.&lt;/p&gt;&#10;&lt;h2 id="the-fix-needed-to-be-part-of-deployment"&gt;&lt;a href="#the-fix-needed-to-be-part-of-deployment" class="header-anchor"&gt;&lt;/a&gt;The Fix Needed to Be Part of Deployment&#10;&lt;/h2&gt;&lt;p&gt;Initially, it might seem sufficient to configure SELinux correctly when building the machine image.&lt;/p&gt;&#10;&lt;p&gt;But that only handles files that exist at image creation time.&lt;/p&gt;&#10;&lt;p&gt;Every application deployment introduces new files, and those files can arrive with contexts inherited from wherever they were prepared.&lt;/p&gt;&#10;&lt;p&gt;That&amp;rsquo;s why the deployment process also needs to handle SELinux labeling.&lt;/p&gt;&#10;&lt;p&gt;After preparing a release, but before activating it, the script restores the expected contexts:&lt;/p&gt;&#10;&lt;div class="code-example"&gt;&#10; &lt;div class="code-toolbar"&gt;&lt;span&gt;Source code&lt;/span&gt;&lt;span class="code-language"&gt;bash&lt;/span&gt;&lt;/div&gt;&#10; &lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo -n /usr/sbin/restorecon -RF &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$RELEASES_DIR&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo -n /usr/sbin/restorecon -RF &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$ROOT_DIR&lt;/span&gt;&lt;span class="s2"&gt;/storage&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;&lt;code&gt;restorecon&lt;/code&gt; uses the configured SELinux file-context rules to apply the expected labels.&lt;/p&gt;&#10;&lt;p&gt;Because the deployment user doesn&amp;rsquo;t have unrestricted privileges, this also required reviewing how the commands would run through &lt;code&gt;sudo&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;The intended approach was to allow only the necessary operations on the relevant application paths, rather than grant broad privileged access.&lt;/p&gt;&#10;&lt;p&gt;Using &lt;code&gt;sudo -n&lt;/code&gt; was deliberate. If the command requires an interactive password, it fails instead of leaving the deployment waiting indefinitely.&lt;/p&gt;&#10;&lt;p&gt;More importantly, if context restoration fails, the deployment shouldn&amp;rsquo;t activate the new release.&lt;/p&gt;&#10;&lt;p&gt;I&amp;rsquo;d rather have a deployment fail clearly than report success and leave the application unable to serve requests.&lt;/p&gt;&#10;&lt;h3 id="why-not-make-the-entire-application-directory-writable"&gt;&lt;a href="#why-not-make-the-entire-application-directory-writable" class="header-anchor"&gt;&lt;/a&gt;Why not make the entire application directory writable?&#10;&lt;/h3&gt;&lt;p&gt;One possible shortcut would be to assign &lt;code&gt;httpd_sys_rw_content_t&lt;/code&gt; to the entire Laravel directory.&lt;/p&gt;&#10;&lt;p&gt;That would probably make several permission problems disappear, but it would also give the web application write access to locations where it doesn&amp;rsquo;t need it.&lt;/p&gt;&#10;&lt;p&gt;Application code should remain read-only from the web process&amp;rsquo;s perspective. Write permissions should be limited to directories that genuinely require them.&lt;/p&gt;&#10;&lt;p&gt;It&amp;rsquo;s a relatively small distinction in configuration, but an important one for maintaining a sensible security boundary.&lt;/p&gt;&#10;&lt;h2 id="validating-the-changes"&gt;&lt;a href="#validating-the-changes" class="header-anchor"&gt;&lt;/a&gt;Validating the Changes&#10;&lt;/h2&gt;&lt;p&gt;After adjusting the deployment process, we checked the application in an AlmaLinux 9.8 environment with SELinux running in Enforcing mode.&lt;/p&gt;&#10;&lt;p&gt;Some of the commands used during validation were:&lt;/p&gt;&#10;&lt;div class="code-example"&gt;&#10; &lt;div class="code-toolbar"&gt;&lt;span&gt;Source code&lt;/span&gt;&lt;span class="code-language"&gt;bash&lt;/span&gt;&lt;/div&gt;&#10; &lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;span class="lnt"&gt;3&#10;&lt;/span&gt;&lt;span class="lnt"&gt;4&#10;&lt;/span&gt;&lt;span class="lnt"&gt;5&#10;&lt;/span&gt;&lt;span class="lnt"&gt;6&#10;&lt;/span&gt;&lt;span class="lnt"&gt;7&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;getenforce&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;matchpathcon -V /var/www/application/current/public/index.php&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;restorecon -nRFv /var/www/application/releases&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ausearch -m AVC,USER_AVC -ts recent -i&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;The paths here have been anonymized. We also checked the release symlink and writable storage.&lt;/p&gt;&#10;&lt;p&gt;The results were encouraging:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;SELinux was running in &lt;code&gt;Enforcing&lt;/code&gt; mode.&lt;/li&gt;&#10;&lt;li&gt;The application health endpoint returned HTTP 200 with a successful JSON status.&lt;/li&gt;&#10;&lt;li&gt;PHP-FPM and Laravel Horizon were active.&lt;/li&gt;&#10;&lt;li&gt;The filesystem contexts we checked showed no mismatches.&lt;/li&gt;&#10;&lt;li&gt;A dry run of &lt;code&gt;restorecon&lt;/code&gt; reported no additional label changes.&lt;/li&gt;&#10;&lt;li&gt;The recent AVC audit query returned no matches.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;That gave us confidence that the tested release was working correctly under SELinux Enforcing.&lt;/p&gt;&#10;&lt;p&gt;Of course, these checks don&amp;rsquo;t cover every possible application behavior. Background jobs, external integrations, less frequently used features, and future releases still need appropriate testing.&lt;/p&gt;&#10;&lt;p&gt;But they provided a useful baseline and confirmed that we didn&amp;rsquo;t need to weaken SELinux to get the application running.&lt;/p&gt;&#10;&lt;h2 id="what-id-still-like-to-improve"&gt;&lt;a href="#what-id-still-like-to-improve" class="header-anchor"&gt;&lt;/a&gt;What I&amp;rsquo;d Still Like to Improve&#10;&lt;/h2&gt;&lt;p&gt;Although the changes made the provisioning process more manageable, I wouldn&amp;rsquo;t consider the work finished yet.&lt;/p&gt;&#10;&lt;p&gt;Package management is the first area I&amp;rsquo;d revisit. The version-handling logic needs to account for RPMs with independent version numbers and resolve related PHP packages as a compatible set. I&amp;rsquo;d also want to verify the installed extensions after any runtime change.&lt;/p&gt;&#10;&lt;p&gt;The second area is dependency availability.&lt;/p&gt;&#10;&lt;p&gt;Moving stable configuration into a golden image reduces the amount of work required at startup, but the revised script still depends on external repositories for some packages.&lt;/p&gt;&#10;&lt;p&gt;That means a repository outage, missing package version, or temporary connectivity issue could still affect instance initialization.&lt;/p&gt;&#10;&lt;p&gt;I&amp;rsquo;d prefer to move more runtime preparation into tested, versioned images. It would mean rebuilding images more frequently, but it would also make instance startup more predictable.&lt;/p&gt;&#10;&lt;p&gt;Finally, I want to improve how we validate the complete provisioning lifecycle.&lt;/p&gt;&#10;&lt;p&gt;A representative test should launch an instance from the intended image, run the initialization script, verify package versions and services, deploy an application release, and check that everything works with SELinux Enforcing.&lt;/p&gt;&#10;&lt;p&gt;I&amp;rsquo;d also include a controlled runtime version change to see how the package-management logic behaves when the installed packages don&amp;rsquo;t match the requested configuration.&lt;/p&gt;&#10;&lt;p&gt;And every deployment should continue restoring SELinux contexts before activating a release, stopping immediately if that operation fails.&lt;/p&gt;&#10;&lt;h2 id="final-thoughts"&gt;&lt;a href="#final-thoughts" class="header-anchor"&gt;&lt;/a&gt;Final Thoughts&#10;&lt;/h2&gt;&lt;p&gt;What started as an effort to simplify a provisioning script turned into a useful review of how we build, initialize, and deploy our application servers.&lt;/p&gt;&#10;&lt;p&gt;The most important lesson for me was how closely these responsibilities are connected.&lt;/p&gt;&#10;&lt;p&gt;A golden image can remove a lot of unnecessary startup work, but only if we clearly define what the image guarantees. Version pinning can improve consistency, but only if we can reliably retrieve the packages we&amp;rsquo;ve pinned. And enabling SELinux is only sustainable when the deployment process handles file contexts correctly.&lt;/p&gt;&#10;&lt;p&gt;For an application running behind an Auto Scaling Group, these details matter. New instances need to behave consistently whether they&amp;rsquo;re launched during a planned deployment, a scale-out event, or recovery from a failure.&lt;/p&gt;&#10;&lt;p&gt;That&amp;rsquo;s ultimately what I&amp;rsquo;m working toward: fewer surprises when an instance starts, fewer external dependencies during recovery, and a provisioning process we can test and trust.&lt;/p&gt;&#10;</description></item></channel></rss>