<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Jose Armesto&#39;s Blog</title>
    <link>https://blog.armesto.net/tags/docker/index.xml</link>
    <description>Recent content on Jose Armesto&#39;s Blog</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>es-es</language>
    <copyright>Powered by [Hugo](//gohugo.io). Theme by [PPOffice](https://github.com/ppoffice).</copyright>
    <atom:link href="https://blog.armesto.net/tags/docker/index.xml" rel="self" type="application/rss+xml" />
    
    <item>
      <title>I didn&#39;t know you could do that with a Dockerfile!</title>
      <link>https://blog.armesto.net/i-didnt-know-you-could-do-that-with-a-dockerfile/</link>
      <pubDate>Tue, 30 Oct 2018 23:47:55 +0000</pubDate>
      
      <guid>https://blog.armesto.net/i-didnt-know-you-could-do-that-with-a-dockerfile/</guid>
      <description>&lt;p&gt;Docker released the &lt;a href=&#34;https://docs.docker.com/develop/develop-images/multistage-build/&#34;&gt;multi-stage builds feature&lt;/a&gt; in the version 17.05. This allowed developers to build smaller Docker images by using a final stage containing the minimum required for the application to work. Even though this is being used more and more over time, there are still multi-stage patterns that are not that widely used.&lt;/p&gt;

&lt;p&gt;In this post I&amp;rsquo;ll show you how to use multi-stage builds to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;avoid having different Dockerfiles for every environment&lt;/li&gt;
&lt;li&gt;copy files from remote images&lt;/li&gt;
&lt;li&gt;use parameters in the &lt;code&gt;FROM&lt;/code&gt; image&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
Multi-stage builds allow us to use the &lt;code&gt;FROM&lt;/code&gt; keyword in several places of the same Dockerfile.
Every time we use the &lt;code&gt;FROM&lt;/code&gt; keyword a new stage will be created.
One important thing is that we can name these stages to refer to them later on in the Dockerfile.&lt;/p&gt;

&lt;h2 id=&#34;you-don-t-need-a-dockerfile-for-each-environment&#34;&gt;You don&amp;rsquo;t need a Dockerfile for each environment&lt;/h2&gt;

&lt;p&gt;Imagine the following scenario: you need certain tools installed on the Docker image that you will use for development, but you don&amp;rsquo;t want those tools on the final image that will be deployed in production.
For example, when developing in PHP it&amp;rsquo;s useful to have &lt;code&gt;xdebug&lt;/code&gt; installed, but you normally don&amp;rsquo;t need it in production.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;# Use this image as the base image for dev and prod.
FROM php:7.2-apache as common

# The pdo_mysql extension is required for both dev and prod.
RUN a2enmod rewrite; \
    chown -R www-data:www-data /var/www/html; \
    docker-php-ext-install pdo_mysql;

# Here we configure PHP, but this configuration will be overwritten for prod.
COPY php.ini /usr/local/etc/php/php.ini


# In this image we will download the dependencies, but without the development dependencies.
# The dependencies are installed in the vendor folder that will later be copied.
FROM composer as builder-dev

WORKDIR /app

# Copy only the files needed to download dependencies to avoid redownloading them when our code changes.
# This will download development/testing dependencies.
COPY composer.json composer.lock /app/
RUN composer install  \
    --ignore-platform-reqs \
    --no-ansi \
    --no-autoloader \
    --no-interaction \
    --no-scripts

# We need to copy our whole application so that we can generate the autoload file inside the vendor folder.
COPY . /app
RUN composer dump-autoload --optimize --classmap-authoritative


# This is the image using in development.
FROM common as dev

# We only install xdebug in development.
RUN pecl install xdebug-2.6.0; \
    docker-php-ext-enable xdebug

WORKDIR /var/www/html

# We enable the errors only in development.
ENV DISPLAY_ERRORS=&amp;quot;On&amp;quot;

# Copy our application.
COPY . /var/www/html/
# Copy the downloaded dependencies from the previous stage.
COPY --from=builder-dev /app/vendor /var/www/html/vendor
# Setup PHP for development.
COPY php-dev.ini /usr/local/etc/php/php.ini
RUN composer dump-autoload --optimize --classmap-authoritative


# In this image we will download the dependencies, but without the development dependencies.
# The dependencies are installed in the vendor folder that will be copied later into the prod image.
FROM composer as builder-prod

WORKDIR /app

COPY composer.json composer.lock /app/
RUN composer install  \
    --ignore-platform-reqs \
    --no-ansi \
    --no-dev \
    --no-autoloader \
    --no-interaction \
    --no-scripts

# We need to copy our whole application so that we can generate the autoload file inside the vendor folder.
COPY . /app
RUN composer dump-autoload --optimize --no-dev --classmap-authoritative

# This is the image that will be deployed on production.
FROM common as prod

# Add label and exposed port for documentation.
LABEL maintainer=&amp;quot;Jose Armesto &amp;lt;jose@armesto.net&amp;gt;&amp;quot;
EXPOSE 80

# No display errors to users in production.
ENV DISPLAY_ERRORS=&amp;quot;Off&amp;quot;

# Copy our application
COPY . /var/www/html/
# Copy the downloaded dependencies from the builder-prod stage.
COPY --from=builder-prod /app/vendor /var/www/html/vendor
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;building-our-application-image&#34;&gt;Building our application image&lt;/h3&gt;

&lt;p&gt;When building the Docker image now we can choose which stage to build. The stages that will be executed depend on which stage we choose to build and the order in which these stages are defined in the Dockerfile.&lt;/p&gt;

&lt;p&gt;If we are developing and need our &lt;code&gt;dev&lt;/code&gt; version of the application, we can build the Docker image like&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ docker build --tag &amp;quot;my-awesome-app&amp;quot; --target &amp;quot;dev&amp;quot; .
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This will only execute the Dockerfile lines of the stage named &lt;code&gt;dev&lt;/code&gt;, and the stages that appear right before it: &lt;code&gt;common&lt;/code&gt; and &lt;code&gt;builder-dev&lt;/code&gt;.
If we would&amp;rsquo;ve placed the &lt;code&gt;builder-prod&lt;/code&gt; stage before the definition of the &lt;code&gt;dev&lt;/code&gt; stage (right after the &lt;code&gt;builder-dev&lt;/code&gt; stage), the &lt;code&gt;builder-prod&lt;/code&gt; stage lines would&amp;rsquo;ve got executed when building the &lt;code&gt;dev&lt;/code&gt; stage, even though we don&amp;rsquo;t need them.
So make sure to plan ahead and put the stages in the right order: every stage related to &lt;code&gt;dev&lt;/code&gt; will be placed before any stage related to &lt;code&gt;prod&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If we need the &lt;code&gt;prod&lt;/code&gt; version that will be deployed in production, we can pass the &lt;code&gt;prod&lt;/code&gt; target or just don&amp;rsquo;t pass any target at all&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ docker build --tag &amp;quot;my-awesome-app&amp;quot; .
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This will execute all the instructions in the Dockerfile, because the &lt;code&gt;prod&lt;/code&gt; stage is the last one to appear on the Dockerfile.&lt;/p&gt;

&lt;h3 id=&#34;can-we-improve-the-building-speed-for-development&#34;&gt;Can we improve the building speed for development?&lt;/h3&gt;

&lt;p&gt;It seems that building our docker image for development is kind of slow. Can we make it faster?
Our approach is copying our application files several times from our laptop to the image layer, making the process slow. And it will be slower as our application grows.&lt;/p&gt;

&lt;p&gt;We can merge the &lt;code&gt;builder-dev&lt;/code&gt; and the &lt;code&gt;dev&lt;/code&gt; stages into one big stage to reduce the number of times we copy our application.
Let&amp;rsquo;s remove the &lt;code&gt;builder-dev&lt;/code&gt; stage and change our &lt;code&gt;dev&lt;/code&gt; stage to this&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;# In the development image we download dependencies and copy our code to the image.
FROM common as dev

# We only want xdebug in development.
# We need to install some tools required by Composer, which will run in this stage.
RUN apt-get update; apt-get install -y wget zip unzip git; \
    pecl install xdebug-2.6.0; \
    docker-php-ext-enable xdebug; \
    wget https://raw.githubusercontent.com/composer/getcomposer.org/1b137f8bf6db3e79a38a5bc45324414a6b1f9df2/web/installer -O - -q | php -- --install-dir=/usr/bin --filename=composer --quiet

WORKDIR /var/www/html

# Copy only the files needed to download dependencies to avoid redownloading them when our code changes
COPY composer.json composer.lock /var/www/html/
RUN composer install  \
    --ignore-platform-reqs \
    --no-ansi \
    --no-autoloader \
    --no-interaction \
    --no-scripts

# We enable the errors only in development
ENV DISPLAY_ERRORS=&amp;quot;On&amp;quot;

# Copy our application. Now the dependencies are already there.
COPY . /var/www/html/
# Setup PHP for development.
COPY php-dev.ini /usr/local/etc/php/php.ini
RUN composer dump-autoload --optimize --classmap-authoritative
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Our development image will contain Composer (and wget, zip&amp;hellip;) too, but I think that&amp;rsquo;s not a big deal in this scenario.&lt;/p&gt;

&lt;h2 id=&#34;parametrized-image-tags&#34;&gt;Parametrized image tags&lt;/h2&gt;

&lt;p&gt;Did you know that you can use parameters for the base image to use when building your image? We can define a parameter that sets the PHP version to use&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;ARG PHP_VERSION=7.2

FROM php:${PHP_VERSION}-apache as common
# Here would go the rest of the Dockerfile
# ...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then just pass the right PHP version to use when building the image. If we want to use PHP v7.1 instead&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ docker build --tag &amp;quot;my-awesome-app&amp;quot; --build-arg &amp;quot;PHP_VERSION=7.1&amp;quot; .
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;combine-targets-with-docker-compose&#34;&gt;Combine targets with docker-compose&lt;/h3&gt;

&lt;p&gt;The reality is that many people use docker-compose while developing locally because it makes it really easy to start other containers along with your application, like a database that your application needs to store information.
In this scenario you can tell docker-compose to build your image and which target to use.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;version: &#39;2.4&#39;
services:
  web:
    build:
      context: .
      target: dev
      args:
        PHP_VERSION: ${PHP_VERSION}
    ports:
      - 8000:80
    volumes:
      - .:/var/www/html
    depends_on:
      - postgres
  postgres:
    image: postgres:11.0-alpine
    environment:
      POSTGRES_USER: dbuser
      POSTGRES_DB: my-db
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can now fire up your application and its dependencies as usual, but you can pass the desired PHP version as an environment variable&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ PHP_VERSION=7.1 docker-compose up --build
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;copying-from-remote-images&#34;&gt;Copying from remote images&lt;/h2&gt;

&lt;p&gt;We have seen how to copy files from previously generated layers.
But did you know that you can copy files from remote images?&lt;/p&gt;

&lt;p&gt;For example, instead of installing Composer in our &lt;code&gt;dev&lt;/code&gt; stage, we could just copy it from the official Composer image&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;# In the development image we download dependencies and copy our code to the image.
FROM common as dev

# We only want xdebug in development.
# We need to install some tools required by Composer, which will run in this stage.
RUN apt-get update; apt-get install -y zip unzip git; \
    pecl install xdebug-2.6.0; \
    docker-php-ext-enable xdebug;

# Copy composer binary from official Composer image.
COPY --from=composer /usr/bin/composer /usr/bin/composer

WORKDIR /var/www/html

# Copy only the files needed to download dependencies to avoid redownloading them when our code changes
COPY composer.json composer.lock /var/www/html/
RUN composer install  \
    --ignore-platform-reqs \
    --no-ansi \
    --no-autoloader \
    --no-interaction \
    --no-scripts

# We enable the errors only in development
ENV DISPLAY_ERRORS=&amp;quot;On&amp;quot;

# Copy our application. Now the dependencies are already there.
COPY . /var/www/html/
# Setup PHP for development.
COPY php-dev.ini /usr/local/etc/php/php.ini
RUN composer dump-autoload --optimize --classmap-authoritative
&lt;/code&gt;&lt;/pre&gt;</description>
    </item>
    
  </channel>
</rss>