A template for Yii 2 applications based on the codemix/yii2-base docker image.
- Ephemeral container, configured via environment variables
- Testing container for easy testing
- Optional local configuration overrides for development/debugging (git-ignored)
- Base scaffold code for login, signup and forgot-password actions
- Flat configuration file structure
- Supports docker image based development workflow
The yii2-base
image comes in three flavours:
- Apache with PHP module (based on
php:5.6.6-apache
) - PHP-FPM (based on
php:5.6.6-fpm
) - HHVM (based on
estebanmatias92/hhvm:3.5.1-fastcgi
)
- Quickstart
You need to have docker (>=1.5.0) and docker-compose installed.
composer create-project --no-install codemix/yii2-dockerized myproject
cd myproject
cp docker-compose-example.yml docker-compose.yml
cp .env-example .env
docker-compose up
# From another terminal window:
docker-compose run --rm web ./yii migrate
It may take some minutes to download the required docker images. When done, you can access the new app from http://localhost:8080.
Note: On Windows and Mac OS you have to use boot2docker. There's also a workaround to make
docker-compose
available.
- How To Use
This project is a template for your own Yii2 applications. You can use it as a starting point and modify it to fit your requirements.
Note: You will do this only once in the lifetime of a project!
As shown in the quickstart you will first create a new project with composer
or, if you don't
have composer installed, download
and uncompress the files into a new directory.
Before you commit the app into your repository, you first will want to set up the project for your purpose.
The first step is to select the yii2-base
image flavour in the Dockerfile
. So uncomment
the apropriate line:
FROM codemix/yii2-base:2.0.4-apache
#FROM codemix/yii2-base:2.0.4-php-fpm
#FROM codemix/yii2-base:2.0.4-hhvm
Then you should check the following files and directories:
Dockerfile
: Optionally add PHP extensionsdocker-compose-example.yml
: Provide an example machine configuration for other developersconfig/
: Update the default configuration.env-example
: Optionally add more environment variablescomposer.json
: Optionally add more dependencies for your projectmigrations/
: Update the initial DB migration to match your DB modelREADME.md
: Describe your application
When you're done, you will commit the changes to your repository to make it available for other developers in your team.
When you have the project in your repository, it's easy to set up a new development environment, e.g. for a new team member:
git clone <your-repo-url> ./myapp
cd myapp
cp docker-compose-example.yml docker-compose.yml
cp .env-example .env
docker-compose up
# From another terminal window:
docker-compose run --rm web ./yii migrate
The local app directory is mapped into the /var/www/html
directory of the web
container. So you can
now work on your local source files and will immediately see the results under the URL of the container.
This template follows the 12 factor principles. This means, that all dynamic configuration parameters (for example database credentials or secret API keys) are passed to the container through environment variables.
There are two options how you can set environment variables during development:
- In the
environment
section of thedocker-compose.yml
. This requires to remove and recreate the container each time you change a variable. So we recommend to only putENABLE_ENV_FILE
here and use the next option for the rest. - In a
.env
file in your app directory. Changes there are picked up immediately by the yii app inside the container. But any other command there will usually ignore this file. We have included an example.env-example
that you can use to get started.
Variables in docker-compose.yml
have precedence over those in the .env
file.
In production you will use whatever means are neccessary to set the environment variables. This depends on how you host your docker image (or if you use docker in production at all).
Note: Some variables can only be set in the
docker-compose.yml
(marked withdc-only
).
Mandatory:
COOKIE_VALIDATION_KEY
the unique cookie validation key required by Yii.
Optional:
API_TOKEN
(dc-only
) the github API token for composer updates.ENABLE_ENV_FILE
(dc-only
) whether to load env vars from a local.env
file. Default is0
.ENABLE_LOCALCONF
whether to allow local overrides for web and console configuration (see below). Default is0
.YII_DEBUG
whether to enable debug mode for Yii. Default is0
.YII_ENV
the Yii app environment. Eitherprod
(default) ordev
. Do not usetest
as this is reserved for the testing container!YII_TRACELEVEL
the traceLevel to use for Yii logging. Default is0
.DB_DSN
the DSN to use for DB connection. Defaults tomysql:host=db;dbname=web
if not set.DB_USER
the DB username. Defaults toweb
if not set.DB_PASSWORD
the DB password. Defaults toweb
if not set.SMTP_HOST
the SMTP hostname.SMTP_USER
the username for the SMTP server.SMTP_PASSWORD
the password for the SMTP server.
All configuration lives in three files in the config/
directory.
web.php
configuration for the web applicationconsole.php
configuration for the console applicationparams.php
application parameters for both web and console application
These three files are committed to your repository. But sometimes it's useful during
development, to override some settings, without accidentally committing them to the
repository. Therefore you can create local override files (requires ENABLE_LOCALCONF
to be set):
local.php
local overrides for the web configurationconsole-local.php
local overrides for the console configuration
Both files are merged with the respective configuration. Thus you can override specific parts of the web or console configuration there.
If you prefer to use the php-fpm base image, you will need two containers, one for the
php-fpm process and one for nginx. There's already a commented out example in the
docker-compose-example.yml
file.
You may also want to modify the Nginx configuration in nginx/nginx.conf
. The most important
setting there is the hostname of the php-fpm container. This must match whatever you have used
in your docker-compose.yml
for the app container. Default is app
:
fastcgi_pass app:9000;
- Testing
We have included a basic test setup that gets you started with testing in no time.
It uses another docker-compose.yml
where the test infrastructure is defined.
Note: If you host more than one yii2-dockerized project on your host you should set the
COMPOSE_PROJECT
envirnoment variable to a unique name for each project, before you run any of the below commands. Otherwhise you will get conflicts as each test container will automatically be called liketests_test_1
due to the sametests/
directory name.
Before you run the tests for the first time, you have to set up the DB:
cd tests/
docker-compose run --rm test ./yii migrate
Note: This may fail the first time because the DB is not up. In this case try again. If this doesn't help either, try to run
docker-compose up
in another terminal window first, then try the above command again.
To write tests, you simply create the respective classes in the acceptance
,
functional
and unit
directories in the tests/codeception
folder. You may
also have to provide Page
classes for acceptance and functional tests in the _pages
directory or add some
fixtures in the
fixtures
directory.
We have included some simple examples that should help you to get started. For further details on how to write tests, please refer to the codeception and Yii 2 documentation.
To run test you only need one simple command inside the tests/
directory:
docker-compose run --rm test
You can also specify a specific codecept
command:
docker-compose run --rm test codecept run functional
The many files in the tests/
directory can be overwhelming. So here's a summary
of what each file and directory is used for.
- tests
- codeception/ the codeception/ directory
- _output/ temporary outputs (gitignored)
- _pages/ pages shared by acceptance and functional tests
- acceptance/ acceptance tests
- config/ Yii app configuration ...
acceptance.php ... for acceptance tests
config.php ... shared by all tests
functional.php ... for functional tests
unit.php ... for unit tests
- fixtures/ fixtures for all tests
- templates/ templates for yii2-faker
- functional/ functional tests
- unit/ unit tests
_bootstrap.php bootstrap file for all tests (load env and Yii.php)
acceptance.suite.yml configuration for acceptance tester
functional.suite.yml configuration for functional tester
unit.suite.yml configuration for unit tester
codeception.yml main configuration for codeception
docker-compose.yml docker compose configuration for testing
yii CLI for the testing environment (migrations, fixtures, ...)
Before you can generate fixtures, you have to provide the respective template
files for yii2-faker in the codeception/templates
directory. Then you can create fixtures with:
docker-compose run --rm test ./yii fixture/generate <tablename>
- Workflows
Docker is very versatile so you can come up with different workflows.
- No docker registry required
- Slower deployment due to extra build step
This is a very simple workflow: In each environment the runtime image is built
from the Dockerfile
. Developers need to be informed whenever the Dockerfile
changes
so that they rebuild their local image.
No images are shared between developers, so the initial and also each consecutive build step
could take quite some time, depending on
how much
the Dockerfile
has changed since the last build in this environment.
- Requires a docker registry (either self-hosted or from 3rd party)
- Quick and simple deployment
Here we can take two approaches: With or without a base image. In both cases
the Dockerfile
should be organized in a way, that the rather static or less changing parts
should be on top of the Dockerfile
and the dynamic or frequently changing
parts (e.g. COPY . /var/www/html
) at the bottom of the file.
We use a single Dockerfile
and each time we want to make a deployment, we create
a new tagged image and push it to the registry. This image can then be pulled to
production.
One drawback here is, that each deployment image contains a full copy of your source code, even if you only changed a couple of lines since the last deployed image.
It's also important that the developers alway pull the last deployed image to their machine, as otherwhise docker couldn't reuse cached layers the next time it builds a new image.
Here we use two Dockerfiles:
- One for the base image, e.g.
Dockerfile.base
and - one for the final image(s), which extends from the base image
The base image stays the same for some time and is used as basis for many deployment images. This has the advantage that (hopefully) many files from the base image can be reused, whenever a new deployment image is built. So the actual release image layer will have a very small footprint and, in effect, once a base image is shared among developers and on production, there's not much data to be moved around with each release.
To start we'd first move the current Dockerfile
and create a base image:
mv Dockerfile Dockerfile.base
docker build -f Dockerfile.base -t myregistry.com:5000/myapp:base-1.0.0
docker push myregistry.com:5000/myapp:base-1.0.0
Now we have a base image as version base-1.0.0
. For ongoing development we take
it from there and use a minimal second Dockerfile
that is based on this image:
FROM myregistry.com:5000/myapp:base-1.0.0
COPY composer.json /var/www/html/
COPY composer.lock /var/www/html/
RUN composer self-update && \
composer install --no-progress
COPY . /var/www/html
RUN mkdir runtime web/assets \
&& chown www-data:www-data runtime web/assets
So other developers will now use this simplified Dockerfile
for their daily work.
They don't have to rebuild all the layers from the base image and will only stack
their local changes on top.
We'll also use this file for the final deployment images:
docker build -t myregistry.com:5000/myapp:1.0.0
docker build -t myregistry.com:5000/myapp:1.0.1
docker build -t myregistry.com:5000/myapp:1.0.2
...
Note: Version numbers here are only used to make the example clearer. You'd probably use a different versioning scheme, especially if you do continous deployments.
After some releases your latest code will differ more and more from the base image. So more and more files will have changed since it was created and this will make your deployment images bigger and bigger with each release. To avoid this you can create an updated base image from your latest code:
docker build -f Dockerfile.base -t myregistry.com:5000/myapp:base-1.1.0
docker push myregistry.com:5000/myapp:base-1.1.0
Note: You may want to modify
Dockerfile.base
to extend from your old base image first. This will save extra space, but add more file layers to your docker images over time, which is limited to 127.
Now we have an updated base image and can use that for ongoing development.
We therefore have to update the FROM
line in the Dockerfile
:
FROM myregistry.com:5000/myapp:base-1.1.0
- FAQ
docker-compose run --rm web yii migrate
docker-compose run --rm web yii mycommand/myaction
Composer packages are installed inside the container under /var/www/vendor
.
So they live outside of the app directory. This is because during development we
usually mount the local app directory into the /var/www/html
directory of the
container. This would override any vendor packages we have there. So you would
either have to locally install all the packages in vendor/
or somehow
make sure, that the composer packages are updated after the local dir was shared.
By keeping the directory outside, we circumvent this issue. The docker images will always contain all required composer dependencies and use those at runtime.
To update or install new packages you first need an updated local composer.lock
file.
Then the next time you issue docker-compose build
it will pick up the changed
file and create a new docker image with the updated packages inside:
docker-compose run --rm web composer update my/package
docker-compose build
Note: If you face the nasty API rate limit issue with github, you can create a personal API token and expose it in your
docker-compose.yml
file asAPI_TOKEN
env var.
Some IDE's may require a copy of the vendor directory somewhere on your local host for autocompleting commands or providing inline help. You can therefore copy the vendor directory to your local directory:
docker-compose run --rm web cp -r /var/www/vendor .
The default log configuration follows the Docker convention to log to STDOUT
and STDERR
. You should better not change this for production. But you can
use a local configuration file as explained above and configure a log route
as usual. If you log to the default directory, you should find the logfiles
in your local runtime/logs/
directory. Note, that this directory will never
be copied into the container, so there's no risk, that you end up with a
bloated image.
Since the codemix/yii2-base image extends from the official php
image, you can use docker-php-ext-install
in your Dockerfile. Here's an example:
RUN apt-get update \
&& apt-get -y install \
libfreetype6-dev \
libjpeg62-turbo-dev \
libmcrypt-dev \
libpng12-dev \
--no-install-recommends \
&& rm -r /var/lib/apt/lists/* \
&& docker-php-ext-install iconv mcrypt \
&& docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
&& docker-php-ext-install gd
For HHVM extension please check the hhvm base image for details.