Connect With Us:

Articles

post thumb
Linux for Composer
by Doug/ on 28 Jul 2020

How To Use Linux for Composer to Deploy to the Cloud

Developers are placed into a tough situation when asked to deploy a working Docker container-based application up to a cloud service. Traditionally, in addition to hours spent wading through cloud services documentation, a deployment was a messy operation involving a combination of docker exec, zip, unzip, sftp and ssh to get everything up and running. In this guide we show you how to deploy directly from your local computer to LfPHP Cloud Services in minutes using a fantastic new tool called Linux for Composer.

What the Heck is Linux for Composer?

At this early stage you may be thinking: well … I’ve heard of Linux, and I’ve heard of Composer … but what the heck is Linux for Composer (LfC)? LfC is yet another incredible tool that comes out of the Linux for PHP project. The brainchild of Foreach Code Factory CEO Andrew Caya, LfC is a PHP package, hosted on github.com and packagist.org, made available via Composer. Obviously any package residing on packagist.org is not Linux, nor the Linux kernel, but what LfC allows you to do is to define a file similar to composer.json (the file Composer uses to specify project dependencies) that includes an extra set of directives that essentially mimics some of the things you can do using Docker Compose. The main difference, however, is that LfC will proceed to not only build the Docker container for you, but actually upload it to a cloud service using credentials you supply. So, effectively, as long as your Docker container works locally, with a single command, that same container is reconstructed instantly live on the Internet. Yes … wow!

What Do I Need to Start?

So if you’re anything like me (e.g. total developer nerd), the next question is … where do I sign up? Glad you asked! (Rhetorical question!) Before you begin the happy next phase of your budding career, you’re going to need to have some software tools installed. If you even got to this point in the article, chances are that you already have most of these items. Just to be on the safe side, here’s the list:

In addition, you’ll need one of the following, depending on your operating system:

  • Unix/Linux/Mac: A bash shell
  • Windows 10: PowerShell
  • Windows 7 – 8 (64 bit only): Docker Toolbox on Windows
  • Windows Vista and earlier: we wish you well, friend! May the Force be with you.

You’ll also need to have some sort of software application you’re working on: one that’s suitable to run in a Docker container. OK … assuming that you’ve got all the prerequisites in place, and have configured a Dockerfile and any associated init scripts, the next step is to set up the cloud service.

What About the Cloud Service?

In order to deploy directly to the cloud, you’ll need a cloud service with a remote API to which LfC can connect. We’re not talking Amazon here folks! There’s no way in … heck … they’re going to open themselves up to that level of competition! Accordingly it’s better to leave the glitzy Amazon department store, and have a look at some of the smaller outfits. Since this article is sponsored by Linux for PHP Cloud Services, by a strange coincidence, they are exactly the outfit we’re going to feature in this article! (Full disclosure: this author is a partner in this venture.)

To start off, create an account on Linux for PHP Cloud Services. Go to this page: https://linuxforphp.com/signup, fill in the blanks, and click Sign Up. You will need to respond to the email in order to activate your account.

The next step is to login and choose Plans. If you hover your mouse over the plan a description pops up. After clicking Buy Now, a more detailed description appears with a server location choice. Although from a connectivity perspective it doesn't matter where the server is located, from a legal perspective, if sensitive customer data will be stored, the physical location of the server may be important.

Prices are extremely reasonable and there are free trial offers available. Promotional codes can be applied when you go to checkout. Now that you’ve got a cloud service upon to contain your application, let’s have a look at granting deployment access.

How Do I Make the Deployment Secure?

Obviously you don’t want any Dick, Jane (or Spot, for that matter!) to be able to deploy code to your website. Accordingly, you’ll need to establish a security token that only allows code deployment from a specific IP address. Please note that the IP address we refer to here, is not the one you see on your own home or office network. The IP address needed is the one that’s visible to the Internet. To find your Internet-visible IP address, first of all, be sure that you are on the computer from which you plan to deploy. You can then simply open a browser to this URL: http://ip2c.org/. Look in the middle of the page for a sentence that reads Your IP is aaa.bbb.ccc.ddd.

You can now return to your Linux for PHP cloud services page for the plan you’ve chosen. From the left side menu choose Access Tokens [1]. Enter your Internet IP address in the input area labeled New IP Address, and click Add [2]. Your new security access token will appear above [3].

Later you will need to copy and paste this into the linuxforcomposer.json file (described below). OK, now that you’ve got access squared away, the next step is to install Linux for Composer.

How Do I Install Linux for Composer?

Linux for Composer is installed using Composer. The installation itself is quite simple. If you installed Composer “globally” on your computer, just create a directory, or from the root of your application project, issue this command:

composer require --dev linuxforphp/linuxforcomposer

Alternatively, if Composer available as a phar file, you would use this alternative:

php composer.phar require --dev linuxforphp/linuxforcomposer

To access Linux for Composer, you have a couple of different options, depending on where you placed the LfC installation:

If You Placed It ...
… within your application project
… in a directory /some/path
Execute Commands Using ...
php vendor/bin/linuxforcomposer.phar
php /some/path/vendor/bin/linuxforcomposer.phar

For further information, have a look at the LfC installation documentation. Here is a screenshot of the installation process:

In order to deploy your app, you next need to configure Linux for PHP.

How Do I Configure Linux for Composer?

Linux for Composer looks for a file named linuxforcomposer.json. This is the primary configuration file, and serves the same purpose as the composer.json file serves for Composer. The complete documentation on all settings in the linuxforcomposer.json file is available here: https://linux-for-composer.readthedocs.io/en/latest/configuration.html.

A quick way to get started is to issue the linuxforcomposer command with an --init flag, which generates a sample configuration file. Be sure you are in the main directory of the project you wish to deploy. Issue the command as follows:

cd /path/to/project
php vendor/bin/linuxforcomposer.phar --init
		

This creates a default linuxforcomposer.json file which you can then modify to suit your application needs.

Linux for Composer JSON File Keys

The first few lines of the default linuxforcomposer.json file appear as follows:

{
	"name": "linuxforphp/linuxforcomposer",
	"description": "A Composer interface to run 'Linux for PHP'  … ",
	// other top-level keys not shown

The top-level definition keys are summarized here:

Within the single top-level linuxforcomposer.json file definition key, there are two primary sub-keys:
single : image {}
single : containers {}
Let's first have a look at defining a single container using LfPHP.

Defining a Container Based Upon the LfPHP Docker Image

Within the definition for single:image{} you can define settings that pertain to the Linux for Composer docker image (LfPHP) by defining the following:

{
  "name": "linuxforphp/linuxforcomposer",
  "description": "A Composer interface to run 'Linux for PHP'  … ",
  "single" : {
    "image" : {
	  "linuxforcomposer" : {
		// other settings not shown
	  }
    }
  }
}

The following table summarizes the single : image : linuxforcomposer settings:

NOTE: the "\" is used to indicate that the option should be all on a single line.

The following table summarizes options for lfphp-get scripts

NOTE: you can also specify zf if you wish a legacy Zend Framework installation, or ze for a legacy Zend Expressive installation (otherwise use laminas or mezzio). As you can see, the automation possibilities inherent using the LfPHP image are staggering. However, in many cases you might be modeling a customer environment not based upon LfPHP, in which case you would need to use the dockerfile key, discussed next.

Here is an example linuxforcomposer.json file that deploys WordPress:

{
  "name": "linuxforphp/linuxforcomposer",
  "description": "'Linux for PHP' example.",
  "single": {
    "image": {
      "linuxforcomposer": {
        "php-versions": ["7.4"],
        "script": ["lfphp-get cms wordpress demo"],
        "thread-safe": "false"
      }
    },
    "containers": {
      "modes": { "mode1": "detached" },
      "ports": {
        "port1": [ "8181:80" ]
      },
      "persist-data": {
        "mount": "false",
        "root-name": "",
        "directories": { }
      }
    }
  },
  "lfphp-cloud": {
    "account": "account_name",
    "username": "user_name",
    "token": "a1b2c3e4e5f6"
  }
}

Defining a Container Based Upon a Custom Dockerfile

If you do not wish to use LfPHP you can also define settings that pertain to a custom Dockerfile. that draws from any image available on dockerhub:

{
  "name": "linuxforphp/linuxforcomposer",
  "description": "A Composer interface to run 'Linux for PHP'  … ",
  "single" : {
    "image" : {
      "dockerfile" : {
        // other settings not shown
      }
    }
  }
}

NOTE: the two sub-keys linuxforcomposer and dockerfile are not mutually exclusive. However, if there is a conflict between the two settings, those defined in dockerfile take precedence. Also, of course, the base image defined in the Dockerfile indicated by the single:image:dockerfile:url sub-key can be any image, but is not restricted to LfPHP. The following table summarizes the single : image : dockerfile settings:

Here is an example linuxforcomposer.json file that deploys a container using a custom Dockerfile:

{
  "name": "linuxforphp/linuxforcomposer",
  "description": "'Linux for PHP' example.",
  "single": {
    "image": {
      "dockerfile": {
        "url": "Dockerfile",
        "container-name": "custom_app",
        "username": "",
        "token": ""            
      }
    },
    "containers": {
      "modes": { "mode1": "detached" },
      "ports": { 
        "port1": [ "8181:80" ],
        "port2": [ "8443:443" ]
      },
      "persist-data": {
        "mount": "false",
        "root-name": "",
        "directories": { }
      }
    }
  },
  "lfphp-cloud": {
    "account": "account_name",
    "username": "user_name",
    "token": "a1b2c3e4e5f6"
  }
}

Now let's have a closer look at defining Docker container runtime settings.

Defining Container Runtime Settings

The single : image : containers sub-key can be used to define container runtime characteristics in much the same manner as docker run. The following table summarizes the single : image : containers sub-keys:

Now that you have an idea what goes into the linuxforcomposer.json file, it's time for an example.

LfPHP Cloud Services Deployment Example

In this example, we have a demonstration app based on Mezzio, a micro-framework from the Laminas project. The name of the project is IpWhat. It can get information on an IP address by making a call to an external web service Ip2C. It also has an internal database, and can present a list of names sorted alphabetically.

The first thing to do is to define the linuxforcomposer.json file.

Sample Linux for Composer JSON File

The file starts with a name and description:

{
  "name": "phpcl_jumpstart_lfphp",
  "description": "Demonstrates deployment using a Dockerfile",

Next we define the single:image block:

  "single": {
    "image": {
      "dockerfile": {
        "url": "Dockerfile",
        "container-name": "phpcl_jumpstart_lfphp",
        "username": "",
        "token": ""
      },

We also add a linuxforcomposer block mainly for added documentation:

      "linuxforcomposer": {
        "php-versions": ["7.4"],
        "script": ["lfphp --mysql --phpfpm --apache"],
        "thread-safe": "false"			
      }
    },

Since we'll be deploying to the cloud service, there's no need for tty nor interactive modes:

    "containers": {
      "modes": {
        "mode1": "detached",
      },

We define port mappings for 80 and 443:

      "ports": {
        "port1": ["8181:80"],
        "port2": ["8443:443"]
      },

And, in this example, no permanent volumes will be mounted:

      "persist-data": {
        "mount": "false",
        "root-name": "",
        "directories": {
          "directory1": "",
          "directory2": "",
          "directory3": ""
        }
      }
    }
  },

Finally, we define the cloud settings:

  "lfphp-cloud": {
    "account": "infoetistacom3",
    "username": "info@etista.com",
    "token": "a1b2c3d4e5f6a1b2c3d4e5f6 "
  }
}

Here is the complete linuxforcomposer.json file used in this example:

{
  "name": "phpcl_jumpstart_lfphp",
  "description": "Demonstrates deployment using a Dockerfile",
  "single": {
    "image": {
      "dockerfile": {
        "url": "Dockerfile",
        "container-name": "phpcl_jumpstart_lfphp",
        "username": "",
        "token": ""
      },
      "linuxforcomposer": {
        "php-versions": ["7.4"],
        "script": ["lfphp --mysql --phpfpm --apache"],
        "thread-safe": "false"			
      }
    },
    "containers": {
      "modes": {
        "mode1": "detached"
      },
      "ports": {
        "port1": ["8181:80"],
        "port2": ["8443:443"]
      },
      "persist-data": {
        "mount": "false",
        "root-name": "",
        "directories": {
          "directory1": "",
          "directory2": "",
          "directory3": ""
        }
      }
    }
  },
  "lfphp-cloud": {
    "account": "infoetistacom3",
    "username": "info@etista.com",
    "token": "a1b2c3d4e5f6a1b2c3d4e5f6 "
  }
}

Now we need to define the Dockerfile.

Example Dockerfile

Although advanced Linux for Composer usage allows for a docker-compose configuration, for the purposes of this guide we follow the single-service-single-container paradigm. Accordingly, all you need to do is to create a normal Dockerfile and confirm that your application runs in its container. The tricky part is that due to the way the container gets created on the cloud service, you will be unable to copy any files into the container as it’s being built. Accordingly, make sure that any external assets you need can be obtained via an Internet download.

To preserve any data, one technique is to store a skeleton structure in your git repo, and have your init script create the database, grant permissions, and restore the table structure. You can then use phpMyAdmin, provided by the cloud service, to restore any data you have backed up. (Er … you did back up your data, didn’t you?) In our sample Dockerfile we first indicate the source of the original image:

FROM asclinux/linuxforphp-8.2-ultimate:7.4-nts

Next, we clone from the repository:

RUN git clone https://github.com/path/to/repo /target/dir/

Note that if your repository is private, generate a security access token, and use the following syntax:

RUN git clone https://user:token@github.com/path/to/repo /target/dir

If you've updated your source code, don't forget to inform the Docker daemon there's been a change … otherwise it will continue to pull from the original branch!

RUN git clone https://github.com/path/to/repo --branch NEW /target/dir/

If you just need a single file that is accessible via HTTP, in your Dockerfile, simply add a directive along these lines:

RUN wget --output-file=FILE http://dns.name.com/path/to/remote/file

You might then wish to run an initialization script (contained in your newly restored repo):

RUN chmod +x /srv/jumpstart/init.sh
RUN /srv/jumpstart/init.sh

After that you will probably need to assign the web server document root to the correct directory path. In this example, the sample application is built using the Mezzio framework (formerly Zend Expressive), so the document root needs to be the public folder off the project root of ip_demo:

RUN \
  cd /srv && \
  mv -f -v /srv/www /srv/www.OLD && \
  ln -s -f -v /srv/jumpstart/ip_demo/public /srv/www
RUN chown apache:apache /srv/www
RUN chown -R apache:apache /srv/jumpstart
RUN chmod -R 775 /srv/jumpstart

Finally, depending on the original source image chosen, you can designate an entry point and followup command. As our source image is Linux for PHP (LfPHP), the entry point is the lfphp command, and our commands are flags that activate MySQL, PHP FastCGI processing module, and Apache:

ENTRYPOINT ["/bin/lfphp"]
CMD ["--mysql", "--phpfpm", "--apache"]

Here is the complete Dockerfile:

FROM asclinux/linuxforphp-8.2-ultimate:7.4-nts
MAINTAINER doug.bierer@etista.com
RUN git clone https://github.com/phpcl/phpcl_jumpstart_lfphp /srv/jumpstart
RUN chmod +x /srv/jumpstart/init.sh
RUN /srv/jumpstart/init.sh
RUN \
  cd /srv && \
  mv -f -v /srv/www /srv/www.OLD && \
  ln -s -f -v /srv/jumpstart/ip_demo/public /srv/www
RUN chown apache:apache /srv/www
RUN chown -R apache:apache /srv/jumpstart
RUN chmod -R 775 /srv/jumpstart
ENTRYPOINT ["/bin/lfphp"]
CMD ["--mysql", "--phpfpm", "--apache"]

NOTE:
to preserve any data, one technique is to store a skeleton structure in your git repo, and have your init script create the database, grant permissions, and restore the table structure. You can then use phpMyAdmin, provided by the cloud service, to restore any data you have backed up. (Er … you did back up your data, didn’t you?).  You can also store data on a persistant volume by configuring the single:image:containers:persist-data key in the linuxforcomposer.json file (described above).

Sample Init Script

The optional init script is a simple BASH (or other shell) script that can perform any desired initialization. In our example, there are two main tasks to be performed: use Composer to install third party software into our demo app, and to initialize the database. We also add the ServerName to the Apache httpd.conf file. Here is the complete script:

#!/bin/bash
echo "ServerName jumpstart" >> /etc/httpd/httpd.conf
cd /srv/jumpstart/ip_demo
php composer.phar self-update
rm -f /srv/jumpstart/ip_demo/composer.lock
php composer.phar install
echo "Restoring database ..."
/etc/init.d/mysql start
sleep 5
mysql -uroot -v -e "CREATE DATABASE jumpstart;"
mysql -uroot -v -e \
  "CREATE USER 'jumpstart'@'localhost' IDENTIFIED BY 'password';"
mysql -uroot -v -e "GRANT ALL PRIVILEGES ON *.* TO 'jumpstart'@'localhost';"
mysql -uroot -v -e "FLUSH PRIVILEGES;"
mysql -uroot -v -e \
  "SOURCE /srv/jumpstart/sample_data/jumpstart.sql;" jumpstart

The next step is to test everything locally.

Testing the Deployment Locally

In order to test the deployment locally, we must have Docker installed. In addition, of course, both Composer and Linux for Composer must be installed. Here is the base project structure listing:

Issue this command, from the root directory of your project where the linuxforcomposer.json file resides, to have Linux for Composer start the container running locally:

php vendor/bin/linuxforcomposer.phar docker:run start

This launches docker build using the Dockerfile specified under the dockerfile:url key as shown here:

Once finished, as we specified detached mode, the container, if built successfully, should now be running:

If we now open a browser to http://localhost:8181/ we should see the demo application running:

If you get a blank screen, or otherwise you note that all is not well, it's time to perform some troubleshooting.

Troubleshooting Your Deployment

If you get a blank screen, you can shell into the running Docker container by making a note of its ID or tag, and issuing the following command:

docker exec -it  /bin/bash

You can then consult the appropriate error log to determine the nature of the error:

If you encounter this error it's most likely that a linuxforcomposer.pid file exists in the vendor/composer directory:

To get rid of this error, stop the running container by moving the project directory and issuing this command:

php vendor/bin/linuxforcomposer.phar docker:run stop

Or, if the container has already been stopped (e.g. using docker container stop), simply delete the /path/to/project/vendor/composer/linuxforcomposer.pid file.

NOTE: One final point is that you might find yourself in the situation where no matter how many times you fix a bug in the application code, you still keep getting the same error or blank screen. This is extremely frustrating and can waste hours and hours of your time.

In this situation, the most likely culprit is one form or another of caching. Here are some suggested remedies:
Push Changes
Make sure you have pushed all changes to the git repo. If your Dockerfile clones from a git repo, and you haven't pushed changes, you're going to keep getting the same buggy code.
Docker Cache
When testing locally, Docker will create a separate layer for each RUN directive in the Dockerfile. One of these will most likely be the layer that clones from the git repo. In this case, no matter how many times you push changes, they never appear when the image is built as Docker logically draws from its own cache. Accordingly you'll need to get rid of the image which forces Docker to rebuild everything.
New Release
If using git clone in your Dockerfile, you can create a new release on github. You can then modify your git clone directive to include the branch or tag associated with the release. Here's an example:

RUN git clone https://github.com/phpcl/phpcl_jumpstart_lfphp \
    --branch v1.0.2 /srv/jumpstart
App Cache
Finally: you application code itself might have caching enabled, and you might accidentally be including this when you push changes to the repo. Create a .gitignore file that excludes any application caching directories.

NOTE: When you use Linux for Composer to stop a running container, you are asked if you wish to commit the change to your local Docker repo. If you choose yes, the next time you use Linux for Composer to run the container, it draws from cache, saving time. Also, very importantly, your repository source code has most likely changed. Docker is unaware of the change, however, unless you change the git clone command in your Dockerfile. Accordingly, it's not a bad idea to either (A) use git tag, or (B) create a new branch, to record your changes. When you've got a new version of the code stored in the repo as either a new branch, or a new tag, you can modify the git clone command in your Dockerfile by adding the --branch or --branch options. When Linux for Composer uploads your revised Dockerfile, the Docker daemon on the cloud service will recognize the change, and pull a fresh copy from the repo instead of using its Docker cache. Now it's time to deploy to the cloud service!

Deploying to Linux for PHP Cloud Services

Once you have the project running successfully on your local computer, it's time to deploy to the cloud service. Double check your IP address and if it's changed for some reason, generate a new access token, as described above. To deploy to the cloud service, from the root directory of your project where the linuxforcomposer.json file resides, issue this command:

php vendor/bin/linuxforcomposer.phar docker:run deploy

If you see this message, double check to make sure your account name, username and token are correctly defined. Also, make sure that your current, valid IP address is at the top of the list:

Otherwise, you should get a success message as shown here:

You will then need to wait until your service has been deployed. The time it takes to run locally should give you an indication how long this will take. In the meantime, monitor your dashboard from your cloud services account.

NOTE: although an initial byte count shows up, possibly in the gigabyte range, this represents what goes to the Docker daemon: not what is uploaded. The only thing uploaded is the Dockerfile.

During deployment the Server Status button [1] will be either yellow, indicating status unknown, or red, indicating server stopped.
Click on Deployment log [2] if you experience problems and the server never comes back up.
Click on Website: View [3] to have a look at your newly deployed application.

Summary

In this guide you learned how to deploy an application using Linux for Composer. You learned about the major configuration settings in the linuxforcomposer.json file, including how to define a single image using either the LfPHP Docker image, or a custom image built from a Dockerfile. In addition you learned settings that correspond to various docker run directives include port and volume mappings. You then went through a sample deployment using a custom Dockerfile deploying directly to Linux for PHP Cloud Services. There was also a discussion on troubleshooting and various error conditions that might arise. As the saying goes … that's all folks. I hope you enjoy using this technology as much as do I. Happy coding and even happier deployment!

Share: