Deploying a Containerized PHP Application to AWS Using Docker and ECS

Since I’ve been playing around with Laravel Envoy, I kept wondering how I could improve the deployment process. For sure it works and I’ve been using it for 6 months or so on a project in production, but having to deal with a physical server has drawbacks I wanted to overcome. For example, the PHP team just released 7.4 packed with cool features I am eager to try. But on a physical server, one has to manually upgrade the PHP version, for each environment and this is a process prone to errors and can lead to different results in different servers. Logistically, the next step was to containerize the application. Because our application is running on an EC2 instance in AWS, I wanted to take advantage of the AWS ecosystem more.

On a high level point of view, this is what I ended up doing: the application is deployed in an ECS cluster running a task that contains a Docker image of the application. This image is stored in ECR. There is an ALB in front of the EC2 instances. The database is stored in RDS. The PHP sessions are stored in a Elasticache memcached server.


Creating a Global Loader Component in Vue.js

In any frontend application, invariably, one has to deal with showing the user an HTTP request is in progress. This is usually done by displaying a spinner while the request is being executed.

As I got bored dealing with Axios callbacks and independent isLoading data property in my Vue.js components, I realized I needed a global solution that would prevent me from cluttering my components with those redundant data properties.

But I also wanted my solution to work with simultaneous requests. And this is where the tricky part comes in, because on most posts I’ve seen, the author assumes you only ever have one API call that toggles a single loading switch. However, in real life, you have have 2, 3 or more API calls to build a page.

You may want to hold off displaying the page until all of the data is there, or maybe you need 2 out of the 3 requests before showing the page. My solution allows you to do both. It is inspired from this post.

Long story short, I created a new Vuex module to store the loading state, that’s determined by the number of requests currently being made. This number is increased or decreased using Axios interceptors, meaning they will be triggered whenever you do a call to axios[get|post|put|patch|delete|etc]. I also added an option that allows you to disable the loader if need be.

Let’s start by reviewing the Vuex module, that I’ll call loader.js:

export const loader = {
    namespaced: true,
    state: {
        loading: false,
        requestsPending: 0,
    actions: {
        show({ commit }) {
        hide({ commit }) {
        pending({ commit }) {
        done({ commit }) {
    mutations: {
        show(state) {
            state.loading = true;
        hide(state) {
            state.loading = false;
        pending(state) {
            if (state.requestsPending === 0) {

        done(state) {
            if (state.requestsPending >= 1) {

            if (state.requestsPending <= 0) {

In a Vue CLI application, this would go under src/store/modules/loader.js. Then I load it in Vuex with the following store/index.js file:

import Vue from 'vue';
import Vuex from 'vuex';
import { loader } from './modules/loader';


export default new Vuex.Store({
    modules: {

Now, we need to setup the Axios interceptors to trigger the state changes automatically.

First, after the Axios import in your main.js file, enable the showLoader option:

axios.defaults.showLoader = true;

Please note there was a bug in the 0.19.0 release of Axios causing custom options being removed, so either use >= 0.19.1 or 0.18.

In the created function of the new Vue instantiation, setup the interceptors as follow:

created() {
        config => {
            if (config.showLoader) {
            return config;
        error => {
            if (error.config.showLoader) {
            return Promise.reject(error);
        response => {
            if (response.config.showLoader) {

            return response;
        error => {
            let response = error.response;

            if (response.config.showLoader) {

            return Promise.reject(error);

This is basically calling the store module we just created to increase the number of requests in progress just before the HTTP call is made, and reducing it when it finishes or an error is encountered.

What comes next depends on your application. Maybe you want to show an overlay when requests are made. maybe you want to remove all the content when a query is made…

Whatever you want to do, you can know if there is a request pending by adding a mapState to your computed block:

computed: {
    ...mapState('loader', ['loading'])

Then, in your layout template or wherever, you can do as follows:

<loader v-if="loading" />
<slot v-else></slot>

My Loader component simply shows a Vuetify v-progress-circular component, but this is of course entirely up to you.

Finally, if for some reason you want to do disable the loader for a specific request, you can do it by passing the showLoader option to Axios like so:

axios.get('api/your-endpoint', { showLoader: false })

I hope you found this useful!


Modernizing a Legacy PHP Application

Update Aug, 11: This post was well received on Reddit, so I added new anti-patterns to reflect the comments.

Recently, I had the chance occasion to work on numerous legacy PHP applications. I spotted common anti-patterns that I had to fix. This article is not about rewriting an old PHP application to <insert shiny framework name here>, but about how to make it more maintainable and less of a hassle to work on.

Anti-pattern #1: Credentials in the Code

This is definitely the most common worst pattern I’ve seen. Many projects simply have sensitive information such as database username and password in the versioned code. This is obviously a bad practice because it makes it impossible to create a local setup as the code is tied to a specific environment, and everybody with an access to the code can see the credentials for what’s usually the production environment.

To fix it, my preferred way to proceed that works with any application is to install vlucas’ phpdotenv package, that allows to create an environment file and access the variables with the environment super variable.

That means we create two files: a .env.example file that will be versioned and serve as a template for the .env file that will hold the credentials. The.env will not be versioned, so add the .env file to your .gitignore. The official documentation explains this well.

Your .env.example will list the credentials:


Your .env will have the credentials:


In the common file, load the .env file:

$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);

Then you can access your credentials using $_ENV['DB_HOST'] for example.

Note that it is not recommended to use the package as is in production, it is better to either:

  • Inject the environment variables at the runtime of your container, if you are using a Docker-based deployment, or in the HTTP server config if possible;
  • Cache the environment variables to avoid the overhead that comes with reading the .env file with every request; this is how Laravel does it.

Files that contained credentials can be purged from the Git history as shown here.

Anti-pattern #2: Not Using Composer

I found it was very common back in the days to have a lib folder with big libraries such as PHPMailer. This is a big no-no when it comes to versioning, so those dependencies should be managed with Composer. This way, you can very easily see which version of a given package you are running, and update them as needed.

So go ahead and start compose require all the things.

Anti-pattern #3: No Local

Most of those applications I worked on only had one environment: production.

But now that you fixed Anti-pattern #1, you should be able to setup a local easily. You still may have some hard coded configuration such as upload paths, but now you can move those over to your .env.

To create new local environments, unsurprisingly I use Docker. I find it especially well-suited for legacy projects as they often use older PHP versions that you do not want to or cannot install.

You can use a service such as PHPDocker or use a light docker-compose.yml file.

Anti-pattern #4: Not Using a Public Folder

I found that most of those old projects are accessible from their root. In other words, any file stored at the root of the project will be publicly readable. This is especially bad when malicious users (ie, kiddies scripts) will try to access included files directly as you may not have defined an exit if the script is accessed directly in all of your included files.

This is obviously incompatible with using a .env file or Composer as it is a bad idea to expose the vendor folder. Yes, there are tweaks to make this possible but when possible, go ahead and move all the client-facing PHP files to a public folder, and alter your web server configuration to make this the root of your application.

In my typical application, I will have:

  • A docker folder for Docker-compose related files (Nginx configuration, PHP Dockerfile, etc)
  • An app folder where the business logic is stored (services, classes, etc)
  • A public folder where the client-facing PHP scripts are stored as well as the assets (JS/CSS). This is the root of the application as far as the client is concerned.
  • .env and .env.example files at the root

Anti-pattern #5: Blatant Security Issues

PHP applications not using a framework, especially old ones, are prone to blatant security issues such as:

  • SQL injections made possible by not escaping parameters in query. Use PDO to prevent this!
  • XSS injections made possible by not escaping user data when it is displayed. Use htmlspecialchars to prevent this.
  • File uploads… a topic of its own. If the developer implemented his own, custom made solution, it is highly possible that it has one or more security issues.
  • CSRF attacks caused by not validating the origin of a request. I recommend Paragonie’s Anti-CSRF package that can be integrated with an existing application easily.
  • Poor password encryption. I have seen many projects still using SHA-1 or even MD5 to hash the passwords. PHP offers good BCrypt support out of the box since 5.5, it would be a shame not to use it. To migrate the passwords painlessly, I like to update the hash in the database as the user logs in. You just need to make sure that the password column is long enough to accomodate BCrypt passwords, a VARCHAR(255) is good enough. Some pseudocode to see how it works:
// Password still in old hash: does not start with $
// Password is correct: convert it and log the user
if (strpos($oldPasswordHash, '$') !== 0 &&
    hash_equals($oldPasswordHash, sha1($clearPasswordInput))) {
    $newPasswordHash = password_hash($clearPasswordInput, PASSWORD_DEFAULT);

    // Update the password column

    // User logged in: return success

// Password already converted
if (password_verify($clearPasswordInput, $currentPasswordHash)) {
    // User logged in: return success

// User not logged in: return failure

Anti-pattern #6: No Tests

This is very common among legacy apps. While I think it is unrealistic to start writing unit tests for the whole application, one can write functional tests.

These are high level tests as shown in the image above, that will help us to ensure that subsequent refactoring of the application does not break it. It can be as simple as, for example, firing up a browser and logging to the application, then expecting a HTTP success code and/or a specific success message in the resulting page. PHPUnit can be used to do that, or other tools like Cypress, or codeception.

Anti-pattern #7: Poor Error Handling

If (or most likely when) something breaks, you want to be aware of it, quickly. But most legacy apps handle errors poorly, relying on PHP’s forgiveness to carry on no matter what.

You want to be able to catch and log as many errors as possible to correct them. There are good articles on the subject.

Throwing specific exceptions will also make it easier for you to locate the location of an error.

Anti-pattern #8: Global Variables

I thought I’d never see those again until I worked on a legacy project. And they make reading and understanding the behavior of code unpredictable. After all, they are evil.

It is better to use Dependency Injection instead, because it gives you control over which instance is used where. A package like Pimple will do the job well.

What to Improve Next?

Depending on the future of the application or the available budget, several more steps can be taken to improve it.

First, if the app is running on an ancient version of PHP (anything below 7), you should really consider updating it. Most of the time it should not be really painful, and the longest task will probably be getting rid of all those mysql_ calls if the application is using them. You can use something like this library for a quick and dirty fix, but the best solution would be to rewrite all the queries to PDO, making sure that the parameters are escaped at the same time.

If the app is not using the MVC pattern, ie having the business logic and the templates separated, it might be a good time to introduce a template library (I know, PHP is a templating language but modern libraries are just much nicer) like Smarty, Twig or Blade.

Finally, if you can afford it, a rewrite to a modern PHP framework such as Laravel or Symfony is much better in the long run. You will have all the tools you need to do safe, sane PHP development. Depending on the size of the application, if it is big, I would advise using the strangler pattern to avoid a big bang rewrite that could (and probably would) end badly. This way, you can migrate parts of the code you are currently working on to the new system while keeping the old working ones untouched until you need to work on them.

This is a cost-efficient approach that allows you to have a modern PHP environment for your day to day tasks while avoiding feature freeze for weeks or months depending on the project.

I will be writing another article on how to do this with Laravel soon.

I hope you enjoyed this article and please feel free to comment below!


Modern PHP Development for WordPress

Let’s face it, WordPress’ reputation among PHP developers is atrocious. It is, for the most part, justified by the questionable code quality of most plugins (because the barrier entry is very low), the will of the core developers to keep backwards compatibility at all cost, and, as a result, its architecture.

However, as you may have noticed, this blog is running WordPress. Why would you ask me? Well, despite all its flaws, WordPress is stupidly easy to use and complete. I considered using Jekyll on GitHub pages but realized that I would miss many features that I like. I can have comments without relying on external services like Disqus, after all I start to appreciate Gutenberg, I do not have to rely on third-party providers for something as simple as a contact form… and I’m OK with running a server for my blog as it would run anyway.

That being said, there are a couple of things you can do to make your developer experience pleasant, or at least way less painful than you would think maintaining a WordPress website.


Using highlight.js on WordPress

I was looking for a replacement for the syntax highlighter I was using on my blog, since SyntaxHighlighter Evolved never really gave me satisfaction (unwanted advertisements next to the code blocks, poor styling).