{"id":17032,"date":"2017-05-05T12:15:26","date_gmt":"2017-05-05T09:15:26","guid":{"rendered":"https:\/\/www.webcodegeeks.com\/?p=17032"},"modified":"2017-05-03T11:30:03","modified_gmt":"2017-05-03T08:30:03","slug":"building-php-command-line-app-docker","status":"publish","type":"post","link":"https:\/\/www.webcodegeeks.com\/devops\/building-php-command-line-app-docker\/","title":{"rendered":"Building a PHP Command Line App with Docker"},"content":{"rendered":"<p>Over the past few months, I\u2019ve started using <a href=\"https:\/\/www.docker.com\/what-docker\">Docker<\/a> for all my local development work. Previously I had experimented with VMs, but because we deal in microservices, running six or more VMs on my Macbook Pro was starting to get cumbersome.<\/p>\n<p>Docker has clear advantages in system utilization, but I also like that my local configuration can easily be used on servers and continuous integration environments as well. I\u2019m not the only developer discovering the advantages to containerized workflows; Docker <a href=\"https:\/\/blog.docker.com\/2016\/02\/docker-hub-two-billion-pulls\/\">has been growing exponentially over the past year and a half<\/a>, and <a href=\"https:\/\/trends.google.com\/trends\/explore?q=%2Fm%2F0wkcjgj\">search volume has been increasing steadily<\/a>.<\/p>\n<p>While most of my work with Docker has been in local development, we recently had our first opportunity to ship a new production service with it. In Part 1 of this tutorial, I\u2019ll walk you through the process of setting up a Laravel PHP command-line application to run in Docker. Look for Part 2 next week, when I\u2019ll go over the process for continuous integration and deployment using <a href=\"https:\/\/codeship.com\/features\/pro\">Codeship Pro<\/a>.<\/p>\n<h2>Project Overview<\/h2>\n<p>At <a href=\"http:\/\/www.thegraidenetwork.com\/\">The Graide Network<\/a>, we needed to automate a bunch of reminder emails and text messages being sent to our users. Every hour, we wanted to go into the database and find any reminders that needed to be sent, then queue up jobs to send each of them.<\/p>\n<p>Since a lot of the code in our project is specific to what we do, I am simplifying it for this tutorial. This guide will cover the key parts of our system and hopefully give you a starting point for developing and deploying a Dockerized PHP command-line app.<\/p>\n<p>Here\u2019s what we\u2019re going to cover in this tutorial:<\/p>\n<ul>\n<li>Setting up a Laravel PHP command-line application<\/li>\n<li>Writing a cron job to run our command every hour within a Docker container<\/li>\n<li>Adding an acceptance test to verify that our command-line job works<\/li>\n<\/ul>\n<p>We will use a network of Docker containers to host a typical <a href=\"https:\/\/laravel.com\/\">Laravel<\/a> application stack: Linux, MySQL, and PHP. Because this app is triggered by the command line only and doesn\u2019t require an HTTP interface, I won\u2019t cover running Apache or NGINX, but that is certainly a good next step if you\u2019re looking to use this in your own application.<\/p>\n<p>If you\u2019d like to see the finished product, <a href=\"https:\/\/github.com\/karllhughes\/docker-php-cli-example\">I\u2019ve made it available on GitHub<\/a>. Okay, let\u2019s get started!<\/p>\n<h3>Prerequisites<\/h3>\n<p>I\u2019m going to assume you have Docker (<a href=\"https:\/\/docs.docker.com\/engine\/getstarted\/step_one\/#step-1-get-docker\">now available for Mac, Windows, or Linux<\/a>) installed. While many developers probably have PHP installed locally, you don\u2019t actually need it to run this application. One of the best things about developing with Docker is that you don\u2019t have to install or update all your languages and tools locally. The containers will take care of that for you.<\/p>\n<p>If you choose to use the GitHub repo and have <a href=\"https:\/\/nodejs.org\/en\/\">Node.js<\/a> installed, you can opt to use the NPM commands. This is not required, but as you will see, it can make your life at the command line slightly easier.<\/p>\n<h2>Creating the Laravel Application<\/h2>\n<p>We\u2019re going to create a simple Laravel command-line application with Docker to run on our local machine.<\/p>\n<p>Laravel is a modern PHP framework with components for <a href=\"https:\/\/laravel.com\/docs\/5.4\/routing\">routing HTTP requests<\/a>, <a href=\"https:\/\/laravel.com\/docs\/5.4\/artisan\">CLI commands<\/a>, <a href=\"https:\/\/laravel.com\/docs\/5.4\/queues\">queues and workers<\/a>, an <a href=\"https:\/\/laravel.com\/docs\/5.4\/eloquent\">ORM<\/a>, and much more. Because Laravel includes most of the features for our application out of the box, the code we have to write is pretty simple.<\/p>\n<h3>Installing Laravel using Docker<\/h3>\n<p>First, let\u2019s <a href=\"https:\/\/laravel.com\/docs\/5.4\/installation\">create a new Laravel application<\/a>. But instead of using a locally installed version of Composer, <a href=\"https:\/\/hub.docker.com\/r\/composer\/composer\/\">we\u2019re going to use the Docker container available on Docker Hub<\/a>:<\/p>\n<pre class=\"brush:php\">$ docker run --rm -v $(pwd):\/app composer\/composer create-project --prefer-dist laravel\/laravel docker-php-cli-example<\/pre>\n<p>This command will download the latest build of the Composer container, create a new Laravel project called <code>docker-php-cli-example<\/code> in your current directory, and install all the composer dependencies.<\/p>\n<h3>Creating an Artisan command<\/h3>\n<p>Next, we need to create an Artisan command. <a href=\"https:\/\/laravel.com\/docs\/5.4\/artisan\">Artisan<\/a> is Laravel\u2019s command-line helper, and any Artisan command you create in your app can be run from the application\u2019s root directory with <code>php artisan YOUR_COMMAND_NAME<\/code>.<\/p>\n<p>To create a new Artisan command using a Docker container (rather than your local version of PHP), navigate to the new folder we created and run:<\/p>\n<pre class=\"brush:php\">$ docker run --rm -v $(pwd):\/app -w \/app php:cli php artisan make:command UpdateNextItem<\/pre>\n<p>This will download the latest <a href=\"https:\/\/hub.docker.com\/_\/php\/\">PHP CLI image<\/a>, put your Laravel app in a volume, and then create a new command in the folder <code>app\/Console\/Commands<\/code>.<\/p>\n<blockquote><p>Because Artisan commands and Composer updates are very common, I usually create some NPM scripts to wrap these long Docker commands. If you\u2019d like, you can see some of them in <a href=\"https:\/\/github.com\/karllhughes\/docker-php-cli-example\/blob\/master\/package.json#L3\">the example project repository I set up on GitHub<\/a>.<\/p><\/blockquote>\n<p>Next, add the newly created command to the <code>app\/Console\/Kernel.php<\/code> class:<\/p>\n<pre class=\"brush:php\">\/** Code for new command **\/\r\n&lt;?php namespace App\\Console;\r\n\r\nuse App\\Console\\Commands\\UpdateNextItem;\r\nuse Illuminate\\Console\\Scheduling\\Schedule;\r\nuse Illuminate\\Foundation\\Console\\Kernel as ConsoleKernel;\r\n\r\nclass Kernel extends ConsoleKernel\r\n{\r\n   \/**\r\n    * The Artisan commands provided by your application.\r\n    *\r\n    * @var array\r\n    *\/\r\n   protected $commands = [\r\n       UpdateNextItem::class,\r\n   ];<\/pre>\n<p>And finally, let\u2019s put some code into the <code>UpdateNextItem<\/code> command we just created:<\/p>\n<pre class=\"brush:php\">\/** Code for new command **\/\r\n&lt;?php namespace App\\Console\\Commands;\r\n\r\nuse App\\Item;\r\nuse Carbon\\Carbon;\r\nuse Illuminate\\Console\\Command;\r\n\r\nclass UpdateNextItem extends Command\r\n{\r\n    \/**\r\n     * The name and signature of the console command.\r\n     *\r\n     * @var string\r\n     *\/\r\n    protected $signature = 'item:update';\r\n    \r\n    \/**\r\n     * The console command description.\r\n     *\r\n     * @var string\r\n     *\/\r\n    protected $description = 'Gets the next item in the database and updates it.';\r\n    \r\n    \/**\r\n     * Execute the console command.\r\n     *\r\n     * @return mixed\r\n     *\/\r\n    public function handle()\r\n    {\r\n        \/\/ Get the next item to be updated\r\n        $nextItem = Item::orderBy('checked_at', 'asc')-&gt;first();\r\n        \/\/ Update the checked_at time\r\n        $nextItem-&gt;checked_at = Carbon::now()-&gt;toDateTimeString();\r\n        \/\/ Save the model\r\n        $nextItem-&gt;save();\r\n        \/\/ Display a message to the command line\r\n        $this-&gt;info(\"Item #{$nextItem-&gt;id} updated\");\r\n    }\r\n}<\/pre>\n<p>At this point, our command won\u2019t run because we haven\u2019t actually created the data model or database migration, so let\u2019s do that next.<\/p>\n<h3>Creating a database migration, seeder, and model<\/h3>\n<p>Laravel also makes it easy to create a database migration and model, so let\u2019s run a command in Docker to generate that:<\/p>\n<pre class=\"brush:php\">$ docker run --rm -v $(pwd):\/app -w \/app php:cli php artisan make:model Item -m<\/pre>\n<p>And now, update the new migration\u2019s <code>up()<\/code> method to contain a <code>checked_at<\/code> column like so:<\/p>\n<pre class=\"brush:php\">public function up()\r\n{\r\n    Schema::create('items', function (Blueprint $table) {\r\n        $table-&gt;increments('id');\r\n        $table-&gt;timestamp('checked_at');\r\n        $table-&gt;timestamps();\r\n    });\r\n}<\/pre>\n<p>You should also remove the default Laravel migrations, as we won\u2019t use users or authentication for this app.<\/p>\n<p>Next, we want to allow the <code>checked_at<\/code> column to be updated by our command, so add this array to the <code>Item.php<\/code> model that we just generated:<\/p>\n<pre class=\"brush:php\">\/**\r\n * The attributes that are mass assignable.\r\n *\r\n * @var array\r\n *\/\r\nprotected $fillable = [\r\n    'checked_at',\r\n];<\/pre>\n<p>Finally, in order to test this new model, we will want to seed some data. Open up the <code>database\/seeds\/DatabaseSeeder.php<\/code> file and update the <code>run()<\/code> method with some randomly generated data:<\/p>\n<pre class=\"brush:php\">\/**\r\n * Run the items database seeder.\r\n *\r\n * @return void\r\n *\/\r\npublic function run()\r\n{\r\n    DB::table('items')-&gt;truncate();\r\n    $numberOfItems = rand(10, 100);\r\n    \r\n    for ($i = 0; $i &lt; $numberOfItems; $i++) {\r\n    \r\n        \/\/ Create a datetime in the past 100 minutes\r\n        $lastChecked = Carbon::now()\r\n            -&gt;subMinutes(rand(1, 100))\r\n            -&gt;toDateTimeString();\r\n            \r\n        \/\/ Insert a new record in the database\r\n        DB::table('items')-&gt;insert([\r\n            'checked_at' =&gt; $lastChecked,\r\n        ]);\r\n    }\r\n}<\/pre>\n<p>Now our Laravel application is mostly complete, but we need a way to quickly spin up a database container and our PHP code at the same time and link them together. So far, we\u2019ve just been using single <code>docker run<\/code> commands, which run a single process and then shut down. While you can run your database and application containers like this as well, I\u2019ve found it much easier to use <a href=\"https:\/\/docs.docker.com\/compose\/\">Docker Compose<\/a>.<\/p>\n<h2>Running the Application with Docker Compose<\/h2>\n<p>Most real applications require several containers running at the same time to work properly, and <a href=\"https:\/\/docs.docker.com\/compose\/\">Docker Compose<\/a> helps you manage multiple linked Docker containers. This application only needs two containers to run: a PHP command line container and a database container.<\/p>\n<p>In this tutorial, we\u2019ll create one custom image to run our application code, and use <a href=\"https:\/\/hub.docker.com\/_\/mariadb\/\">MariaDB\u2019s standard image<\/a> for our database. Then we\u2019ll use Docker Compose to run them as containers.<\/p>\n<h3>Adding a cron job<\/h3>\n<p>Before we build the Dockerfile for our application, we need to add a simple cron job to run our artisan command automatically. Create a new folder called <code>docker<\/code> and a file within it called <code>crontab<\/code> in your project. The crontab file should have one command with an empty line after:<\/p>\n<pre class=\"brush:php\">0 * * * * root \/usr\/local\/bin\/php \/app\/artisan item:update<\/pre>\n<p>This will run the artisan command <code>item:update<\/code> every hour when implemented within this container.<\/p>\n<h3>Creating a Dockerfile for our application<\/h3>\n<p>Next, we\u2019ll create a Dockerfile for our PHP application. The Dockerfile is a series of commands that will be used to build an image, and that image will be run as a container. This Dockerfile should go in the root directory of your project:<\/p>\n<pre class=\"brush:php\">FROM php:cli\r\n\r\n# Install mysql and cron\r\nRUN apt-get update &amp;&amp; apt-get install -y \\\r\n    libpq-dev \\\r\n    cron \\\r\n    mysql-client\r\nRUN docker-php-ext-install pdo pdo_mysql\r\n\r\n# Add crontab file in the cron directory\r\nADD docker\/crontab \/etc\/cron.d\/cron\r\n\r\n# Give execution rights on the cron job\r\nRUN chmod 0644 \/etc\/cron.d\/cron\r\n\r\nADD .\/ \/app\r\n\r\nWORKDIR \/app<\/pre>\n<p>When built, the Dockerfile starts with the PHP CLI image as a base, then adds MySQL drivers, the cron job runner, and our <code>crontab<\/code> file that we created in the last step. It also copies the project directory into the container.<\/p>\n<h3>Updating the .env file<\/h3>\n<p>Both Laravel and Docker support <code>.env<\/code> files, so in order to keep things simple, we\u2019re going to create a .env file for both:<\/p>\n<pre class=\"brush:php\">APP_ENV=local\r\nAPP_KEY=YOUR_LARAVEL_APP_KEY\r\nAPP_DEBUG=true\r\nAPP_LOG_LEVEL=debug\r\nAPP_URL=http:\/\/localhost\r\n\r\nDB_CONNECTION=mysql\r\nDB_HOST=database\r\nDB_PORT=3306\r\nDB_DATABASE=laravel\r\nDB_USERNAME=root\r\nDB_PASSWORD=a_good_strong_password\r\nMYSQL_ROOT_PASSWORD=a_good_strong_password<\/pre>\n<p>The most important part is the database connection details. We also need to set a MySQL root password. If you\u2019re planning on using this project in production, it would be a good idea to modify it to create a new MySQL user instead of running as root, but we\u2019re going to keep it simple for the time being.<\/p>\n<h3>The docker-compose.yml file<\/h3>\n<p>Now that we have a Dockerfile and our configuration files set up, we can use a Docker Compose file to bring the whole thing up. At the root of your directory, create a file called <code>docker-compose.yml<\/code>:<\/p>\n<pre class=\"brush:php\">version: \"2.0\"\r\nservices:\r\n  # PHP Application\r\n  app:\r\n    build: .\r\n    links:\r\n      - database\r\n    env_file: .env\r\n    command: cron -f\r\n  # Database\r\n  database:\r\n    image: mariadb\r\n    env_file: .env<\/pre>\n<p>This Compose file basically does three things:<\/p>\n<ol>\n<li>Creates the database container from the MariaDB image on Docker Hub.<\/li>\n<li>Creates the application container from our local <code>Dockerfile<\/code> that we just created.<\/li>\n<li>Links them together. It also gives our application container a command to run at startup (<code>cron -f<\/code>), which will initialize the cron job that runs every hour.<\/li>\n<\/ol>\n<p>I should also note that this Compose file is the bare minimum to get running. There are <a href=\"https:\/\/docs.docker.com\/compose\/compose-file\/compose-file-v2\/\">a ton of configuration options available<\/a> including mounting volumes for your code or data, restarting your containers automatically, and connecting to existing Docker networks. In a real application, you may have multiple Compose files for different environments, but hopefully this one will get you started.<\/p>\n<h3>Manually testing it out<\/h3>\n<p>You are now ready to try your application out and see if everything works.<\/p>\n<p>Since this is the first time running it, we want to see what\u2019s going on. Open one terminal window and run <code>$ docker-compose up --build<\/code>. This will build the local application image, pull down the latest image for MariaDB, and run the containers in the terminal window. Usually you will want to run the \u201cup\u201d command with the <code>-d<\/code> flag to make it run in the background, but since it\u2019s your first time, it might be helpful to see what\u2019s going on.<\/p>\n<p>Next, you can use another terminal window to see which containers are running: <code>$ docker ps<\/code>.<\/p>\n<p>You should see something like this:<\/p>\n<pre class=\"brush:php\">CONTAINER ID        IMAGE                     COMMAND                  CREATED              STATUS              PORTS               NAMES\r\n597ab6be26d6        dockerphpcliexample_app   \"docker-php-entryp...\"   About a minute ago   Up About a minute                       dockerphpcliexample_app_1\r\nd61f7f36d0ba        mariadb                   \"docker-entrypoint...\"   4 minutes ago        Up About a minute   3306\/tcp            dockerphpcliexample_database_1<\/pre>\n<p>This means that both of your containers started properly and are running.<\/p>\n<p>Every time you bring up a container, it\u2019s like you installed everything fresh, so you\u2019ll need to configure your database and run the migrations. To create your database:<\/p>\n<pre class=\"brush:php\">$ docker exec dockerphpcliexample_app_1 mysql -h'database' -p'a_good_strong_password' -e 'CREATE DATABASE IF NOT EXISTS laravel'<\/pre>\n<p>It can take from a few seconds to a minute or two to bring up MariaDB. If you get an error like <code>Can't connect to MySQL server on 'database' (111)<\/code> the first time you try this command, wait a minute and try again.<\/p>\n<p>To run the migration we created:<\/p>\n<pre class=\"brush:php\">$ docker exec dockerphpcliexample_app_1 php artisan migrate<\/pre>\n<p>To seed the database:<\/p>\n<pre class=\" brush:php\">$ docker exec dockerphpcliexample_app_1 php artisan db:seed<\/pre>\n<p>And finally, if all those commands went through okay, you can try running the Artisan command we created way back at the beginning of this tutorial:<\/p>\n<pre class=\"brush:php\">$ docker exec dockerphpcliexample_app_1 php artisan item:update<\/pre>\n<p>You should get a result like <code>Item #12 updated<\/code> each time you run the command. When you\u2019re done, bring down your Docker containers by running <code>docker-compose down<\/code>.<\/p>\n<h3>Automated testing<\/h3>\n<p>Continuous integration isn\u2019t as much use without some tests to run, so we\u2019re going to add a single acceptance test that verifies that our application\u2019s single artisan command will run when the database is set up and seeded. In real life, our test suite will likely be more complicated than this, but for the sake of simplicity, let\u2019s just add one test in the <code>tests\/Feature\/Example.php<\/code> file:<\/p>\n<pre class=\"brush:php\">\/** adding a test **\/\r\n&lt;?php namespace Tests\\Feature; \r\n\r\nuse Illuminate\\Support\\Facades\\Artisan;\r\nuse Tests\\TestCase;\r\n\r\nclass ExampleTest extends TestCase\r\n{\r\n    public function setUp()\r\n    {\r\n        \/\/ Seed the database before each run\r\n        parent::setUp();\r\n        Artisan::call('db:seed', array('--class'=&gt;'DatabaseSeeder'));\r\n    }\r\n    \/**\r\n     * Run the artisan command\r\n     *\r\n     * @return void\r\n     *\/\r\n    public function testItCanRunItemUpdate()\r\n    {\r\n        Artisan::call('item:update');\r\n        $this-&gt;assertRegExp(\"\/Item #[0-9]* updated\\\\n\/\", Artisan::output());\r\n    }\r\n}<\/pre>\n<p>Now, to run the test, we need to bring our containers back up, create the database, run the migrations, and then finally run the test suite:<\/p>\n<pre class=\"brush:php\">$ docker-compose up -d --build\r\n## You may need to wait a few seconds for the database container to come up\r\n$ docker exec dockerphpcliexample_app_1 mysql -h'database' -p'a_good_strong_password' -e 'CREATE DATABASE IF NOT EXISTS laravel'\r\n$ docker exec dockerphpcliexample_app_1 php artisan migrate\r\n$ docker exec dockerphpcliexample_app_1 vendor\/bin\/phpunit\r\nPHPUnit 5.7.19 by Sebastian Bergmann and contributors.\r\n\r\n.                                                                   1 \/ 1 (100%)\r\n\r\nTime: 1.04 seconds, Memory: 12.00MB\r\n\r\nOK (1 test, 1 assertion)<\/pre>\n<p>Now we have a working PHP command-line application with tests, but as you can see, these Docker commands are pretty long and annoying to type out over and over again. I like to wrap them in NPM scripts (<a href=\"https:\/\/github.com\/karllhughes\/docker-php-cli-example\/blob\/master\/package.json\">click here for an example<\/a>), but this is totally optional. There\u2019s a valid argument <em>against<\/em> abstracting away Docker commands, as it can prevent new developers from knowing what\u2019s going on behind the scenes. But once you do know how each command works, there\u2019s no harm in making your life easier.<\/p>\n<p>Be sure to read Part 2 of this tutorial to learn how to take advantage of Codeship Pro <a href=\"https:\/\/blog.codeship.com\/codeship-ui-ux-updates\/\">(freshly updated with a new UI)<\/a> for continuous integration and deployment. If you have any problems running this tutorial, feel free to <a href=\"https:\/\/github.com\/karllhughes\/docker-php-cli-example\">add an issue to the GitHub repository I set up<\/a> or <a href=\"https:\/\/twitter.com\/KarlLHughes\">find me on Twitter<\/a>.<\/p>\n<div class=\"attribution\">\n<table>\n<tbody>\n<tr>\n<td><span class=\"reference\">Reference: <\/span><\/td>\n<td><a href=\"https:\/\/blog.codeship.com\/building-a-php-command-line-app-with-docker\/\">Building a PHP Command Line App with Docker<\/a> from our <a href=\"http:\/\/www.webcodegeeks.com\/join-us\/wcg\/\">WCG partner<\/a> Karl Hughes at the <a href=\"http:\/\/blog.codeship.com\/\">Codeship Blog<\/a> blog.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Over the past few months, I\u2019ve started using Docker for all my local development work. Previously I had experimented with VMs, but because we deal in microservices, running six or more VMs on my Macbook Pro was starting to get cumbersome. Docker has clear advantages in system utilization, but I also like that my local &hellip;<\/p>\n","protected":false},"author":204,"featured_media":10356,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[17],"tags":[217,122],"class_list":["post-17032","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops","tag-docker","tag-php"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.5 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Building a PHP Command Line App with Docker - Web Code Geeks - 2026<\/title>\n<meta name=\"description\" content=\"Over the past few months, I\u2019ve started using Docker for all my local development work. Previously I had experimented with VMs, but because we deal in\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.webcodegeeks.com\/devops\/building-php-command-line-app-docker\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Building a PHP Command Line App with Docker - Web Code Geeks - 2026\" \/>\n<meta property=\"og:description\" content=\"Over the past few months, I\u2019ve started using Docker for all my local development work. Previously I had experimented with VMs, but because we deal in\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.webcodegeeks.com\/devops\/building-php-command-line-app-docker\/\" \/>\n<meta property=\"og:site_name\" content=\"Web Code Geeks\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/webcodegeeks\" \/>\n<meta property=\"article:published_time\" content=\"2017-05-05T09:15:26+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2016\/01\/docker-logo.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"150\" \/>\n\t<meta property=\"og:image:height\" content=\"150\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Karl Hughes\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@webcodegeeks\" \/>\n<meta name=\"twitter:site\" content=\"@webcodegeeks\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Karl Hughes\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"13 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.webcodegeeks.com\/devops\/building-php-command-line-app-docker\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/devops\/building-php-command-line-app-docker\/\"},\"author\":{\"name\":\"Karl Hughes\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/f648cc42fbabad7d401f57485bdcbe28\"},\"headline\":\"Building a PHP Command Line App with Docker\",\"datePublished\":\"2017-05-05T09:15:26+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/devops\/building-php-command-line-app-docker\/\"},\"wordCount\":1998,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#organization\"},\"image\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/devops\/building-php-command-line-app-docker\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2016\/01\/docker-logo.jpg\",\"keywords\":[\"Docker\",\"php\"],\"articleSection\":[\"DevOps\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/www.webcodegeeks.com\/devops\/building-php-command-line-app-docker\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.webcodegeeks.com\/devops\/building-php-command-line-app-docker\/\",\"url\":\"https:\/\/www.webcodegeeks.com\/devops\/building-php-command-line-app-docker\/\",\"name\":\"Building a PHP Command Line App with Docker - Web Code Geeks - 2026\",\"isPartOf\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/devops\/building-php-command-line-app-docker\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/devops\/building-php-command-line-app-docker\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2016\/01\/docker-logo.jpg\",\"datePublished\":\"2017-05-05T09:15:26+00:00\",\"description\":\"Over the past few months, I\u2019ve started using Docker for all my local development work. Previously I had experimented with VMs, but because we deal in\",\"breadcrumb\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/devops\/building-php-command-line-app-docker\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.webcodegeeks.com\/devops\/building-php-command-line-app-docker\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.webcodegeeks.com\/devops\/building-php-command-line-app-docker\/#primaryimage\",\"url\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2016\/01\/docker-logo.jpg\",\"contentUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2016\/01\/docker-logo.jpg\",\"width\":150,\"height\":150},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.webcodegeeks.com\/devops\/building-php-command-line-app-docker\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.webcodegeeks.com\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"DevOps\",\"item\":\"https:\/\/www.webcodegeeks.com\/category\/devops\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Building a PHP Command Line App with Docker\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#website\",\"url\":\"https:\/\/www.webcodegeeks.com\/\",\"name\":\"Web Code Geeks\",\"description\":\"Web Developers Resource Center\",\"publisher\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.webcodegeeks.com\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#organization\",\"name\":\"Exelixis Media P.C.\",\"url\":\"https:\/\/www.webcodegeeks.com\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png\",\"contentUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png\",\"width\":864,\"height\":246,\"caption\":\"Exelixis Media P.C.\"},\"image\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/webcodegeeks\",\"https:\/\/x.com\/webcodegeeks\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/f648cc42fbabad7d401f57485bdcbe28\",\"name\":\"Karl Hughes\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/7f7dd9296189dc66403a47c73e1a7ba97c94bd7858054af38c0eb9548d814bd8?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/7f7dd9296189dc66403a47c73e1a7ba97c94bd7858054af38c0eb9548d814bd8?s=96&d=mm&r=g\",\"caption\":\"Karl Hughes\"},\"description\":\"Karl Hughes is a startup fanatic, engineering team lead, and CTO at The Graide Network.\",\"url\":\"https:\/\/www.webcodegeeks.com\/author\/karl-hughes\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Building a PHP Command Line App with Docker - Web Code Geeks - 2026","description":"Over the past few months, I\u2019ve started using Docker for all my local development work. Previously I had experimented with VMs, but because we deal in","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.webcodegeeks.com\/devops\/building-php-command-line-app-docker\/","og_locale":"en_US","og_type":"article","og_title":"Building a PHP Command Line App with Docker - Web Code Geeks - 2026","og_description":"Over the past few months, I\u2019ve started using Docker for all my local development work. Previously I had experimented with VMs, but because we deal in","og_url":"https:\/\/www.webcodegeeks.com\/devops\/building-php-command-line-app-docker\/","og_site_name":"Web Code Geeks","article_publisher":"https:\/\/www.facebook.com\/webcodegeeks","article_published_time":"2017-05-05T09:15:26+00:00","og_image":[{"width":150,"height":150,"url":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2016\/01\/docker-logo.jpg","type":"image\/jpeg"}],"author":"Karl Hughes","twitter_card":"summary_large_image","twitter_creator":"@webcodegeeks","twitter_site":"@webcodegeeks","twitter_misc":{"Written by":"Karl Hughes","Est. reading time":"13 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.webcodegeeks.com\/devops\/building-php-command-line-app-docker\/#article","isPartOf":{"@id":"https:\/\/www.webcodegeeks.com\/devops\/building-php-command-line-app-docker\/"},"author":{"name":"Karl Hughes","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/f648cc42fbabad7d401f57485bdcbe28"},"headline":"Building a PHP Command Line App with Docker","datePublished":"2017-05-05T09:15:26+00:00","mainEntityOfPage":{"@id":"https:\/\/www.webcodegeeks.com\/devops\/building-php-command-line-app-docker\/"},"wordCount":1998,"commentCount":0,"publisher":{"@id":"https:\/\/www.webcodegeeks.com\/#organization"},"image":{"@id":"https:\/\/www.webcodegeeks.com\/devops\/building-php-command-line-app-docker\/#primaryimage"},"thumbnailUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2016\/01\/docker-logo.jpg","keywords":["Docker","php"],"articleSection":["DevOps"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.webcodegeeks.com\/devops\/building-php-command-line-app-docker\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.webcodegeeks.com\/devops\/building-php-command-line-app-docker\/","url":"https:\/\/www.webcodegeeks.com\/devops\/building-php-command-line-app-docker\/","name":"Building a PHP Command Line App with Docker - Web Code Geeks - 2026","isPartOf":{"@id":"https:\/\/www.webcodegeeks.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.webcodegeeks.com\/devops\/building-php-command-line-app-docker\/#primaryimage"},"image":{"@id":"https:\/\/www.webcodegeeks.com\/devops\/building-php-command-line-app-docker\/#primaryimage"},"thumbnailUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2016\/01\/docker-logo.jpg","datePublished":"2017-05-05T09:15:26+00:00","description":"Over the past few months, I\u2019ve started using Docker for all my local development work. Previously I had experimented with VMs, but because we deal in","breadcrumb":{"@id":"https:\/\/www.webcodegeeks.com\/devops\/building-php-command-line-app-docker\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.webcodegeeks.com\/devops\/building-php-command-line-app-docker\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.webcodegeeks.com\/devops\/building-php-command-line-app-docker\/#primaryimage","url":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2016\/01\/docker-logo.jpg","contentUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2016\/01\/docker-logo.jpg","width":150,"height":150},{"@type":"BreadcrumbList","@id":"https:\/\/www.webcodegeeks.com\/devops\/building-php-command-line-app-docker\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.webcodegeeks.com\/"},{"@type":"ListItem","position":2,"name":"DevOps","item":"https:\/\/www.webcodegeeks.com\/category\/devops\/"},{"@type":"ListItem","position":3,"name":"Building a PHP Command Line App with Docker"}]},{"@type":"WebSite","@id":"https:\/\/www.webcodegeeks.com\/#website","url":"https:\/\/www.webcodegeeks.com\/","name":"Web Code Geeks","description":"Web Developers Resource Center","publisher":{"@id":"https:\/\/www.webcodegeeks.com\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.webcodegeeks.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.webcodegeeks.com\/#organization","name":"Exelixis Media P.C.","url":"https:\/\/www.webcodegeeks.com\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/","url":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png","contentUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png","width":864,"height":246,"caption":"Exelixis Media P.C."},"image":{"@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/webcodegeeks","https:\/\/x.com\/webcodegeeks"]},{"@type":"Person","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/f648cc42fbabad7d401f57485bdcbe28","name":"Karl Hughes","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/7f7dd9296189dc66403a47c73e1a7ba97c94bd7858054af38c0eb9548d814bd8?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/7f7dd9296189dc66403a47c73e1a7ba97c94bd7858054af38c0eb9548d814bd8?s=96&d=mm&r=g","caption":"Karl Hughes"},"description":"Karl Hughes is a startup fanatic, engineering team lead, and CTO at The Graide Network.","url":"https:\/\/www.webcodegeeks.com\/author\/karl-hughes\/"}]}},"_links":{"self":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/posts\/17032","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/users\/204"}],"replies":[{"embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/comments?post=17032"}],"version-history":[{"count":0,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/posts\/17032\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/media\/10356"}],"wp:attachment":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/media?parent=17032"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/categories?post=17032"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/tags?post=17032"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}