Skip to content

Commit 2728117

Browse files
committed
Containerize the application
Set up tools to easily use docker to run our application in prd, dev and tst environments. We use a base dockerfile to set up a base image for our project, with all the system requirements as these are the ones changing less often. This way we do not have to install all these dependencies every time we build a new prd image to deploy. prd is the production environment although running on the builtin php web server, it is enough for the purposes of this project, for the time being. We made "symfony/web-server-bundle" a requirement so that we can run it for production.The generated container image is completely self contained, it contains all, and only, the code needed to run on prd. There is one volume configured so that we can have persistent state between deployments (sqlite DB). dev is used during development and includes xdebug. It also has a volume configured so we can change the project files in the host and have the container immediately run them without needing to create a new container. tst is the environment used to run the tests during development. As the dev environment it uses a volume to run tests on the src files under development and has xdebug configured so we can debug the tests.
1 parent 0dddcd5 commit 2728117

26 files changed

+567
-122
lines changed

.dockerignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
no-vcs
2+
var
3+
.env

.env.dist

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
# Set variables here that may be different on each deployment target of the app, e.g. development, staging, production.
33
# https://symfony.com/doc/current/best_practices/configuration.html#infrastructure-related-configuration
44

5+
ENV=dev
6+
57
###> symfony/framework-bundle ###
68
APP_ENV=dev
79
APP_DEBUG=1

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
/build/
21
/node_modules/
32
/var/*
43
!/var/cache

Makefile

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,18 @@
77
#
88
# For example in a format like `subject-action-environment`, ie:
99
#
10+
# box-build-base: # box is a generic term, we don't care if it is a virtual machine or a container
11+
# box-build-ci:
12+
# box-build-dev:
13+
# box-push-base:
14+
# box-push-prd:
1015
# cs-fix: # here we don't use the env because we only do it in dev
1116
# dep-install: # again, by default the env is dev
17+
# dep-install-ci:
1218
# dep-install-prd:
1319
# dep-update:
1420
# test: # here we don't even have a subject because it is the app itself, and by default the env is dev
21+
# test-ci: # here we don't even have a subject because it is the app itself
1522
#
1623

1724
# Mute all `make` specific output. Comment this out to get some debug information.
@@ -37,42 +44,76 @@ help:
3744

3845
########################################################################################################################
3946

47+
CONTAINER_NAME_BASE="hgraca/explicit-architecture:app.sfn.base"
48+
CONTAINER_NAME_PRD="hgraca/explicit-architecture:app.sfn.prd"
4049
COVERAGE_REPORT_PATH="var/coverage.clover.xml"
4150
DB_PATH='var/data/blog.sqlite'
4251

52+
box-build-base:
53+
docker build -t ${CONTAINER_NAME_BASE} -f ./build/container/app.base.dockerfile .
54+
55+
box-build-dev:
56+
docker-compose -f build/container/dev/docker-compose.yml build --force-rm app
57+
58+
box-build-prd:
59+
docker-compose -f build/container/prd/docker-compose.yml build --force-rm app
60+
61+
box-push-base:
62+
docker push ${CONTAINER_NAME_BASE}
63+
64+
box-push-prd:
65+
docker push ${CONTAINER_NAME_PRD}
66+
4367
# We run this in tst ENV so that we never run it with xdebug on
4468
cs-fix:
45-
php vendor/bin/php-cs-fixer fix --verbose
69+
ENV='tst' ./bin/run php vendor/bin/php-cs-fixer fix --verbose
4670

4771
db-setup:
48-
mkdir -p var/data
72+
ENV='dev' ./bin/run make db-setup-guest
73+
74+
db-setup-guest:
75+
mkdir -p ./var/data
4976
php bin/console doctrine:database:drop -n --force
5077
php bin/console doctrine:database:create -n
5178
php bin/console doctrine:schema:create -n
5279
php bin/console doctrine:fixtures:load -n
5380

54-
dep-clearcache:
81+
dep-clearcache-guest:
5582
composer clearcache
5683

5784
dep-install:
58-
composer install
85+
ENV='dev' ./bin/run composer install
5986

60-
dep-install-prd:
87+
# We use this only when building the box used in PRD
88+
dep-install-prd-guest:
6189
composer install --no-dev --optimize-autoloader --no-ansi --no-interaction --no-progress --no-scripts
6290

6391
dep-update:
64-
composer update
92+
ENV='dev' ./bin/run composer update
93+
94+
shell:
95+
docker exec -it app.sfn.dev sh
6596

6697
test:
67-
$(MAKE) db-setup
68-
php vendor/bin/phpunit
98+
- ENV='tst' ./bin/stop # Just in case some container is left over stopped, as is the case after PHPStorm runs tests
99+
ENV='tst' ./bin/run
100+
ENV='tst' ./bin/run make db-setup-guest
101+
ENV='tst' ./bin/run php vendor/bin/phpunit
69102
$(MAKE) cs-fix
103+
ENV='tst' ./bin/stop
70104

71105
# We use phpdbg because is part of the core and so that we don't need to install xdebug just to get the coverage.
72106
# Furthermore, phpdbg gives us more info in certain conditions, ie if the memory_limit has been reached.
73107
test_cov:
74-
phpdbg -qrr vendor/bin/phpunit --coverage-text --coverage-clover=${COVERAGE_REPORT_PATH}
108+
ENV='tst' ./bin/run phpdbg -qrr vendor/bin/phpunit --coverage-text --coverage-clover=${COVERAGE_REPORT_PATH}
75109

76110
up:
77111
if [ ! -f ${DB_PATH} ]; then $(MAKE) db-setup; fi
78-
php bin/console server:run 0.0.0.0:8000
112+
$(eval UP=ENV=dev docker-compose -f build/container/dev/docker-compose.yml up -t 0)
113+
$(eval DOWN=ENV=dev docker-compose -f build/container/dev/docker-compose.yml down -t 0)
114+
- bash -c "trap '${DOWN}' EXIT; ${UP}"
115+
116+
up-prd:
117+
$(eval UP=ENV=prd docker-compose -f build/container/prd/docker-compose.yml up -t 0)
118+
$(eval DOWN=ENV=prd docker-compose -f build/container/prd/docker-compose.yml down -t 0)
119+
- bash -c "trap '${DOWN}' EXIT; ${UP}"

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,5 +61,19 @@ $ cd symfony-demo/
6161
$ make test_cov
6262
```
6363

64+
Integration with PHPStorm
65+
-------------------------
66+
67+
Integration with PHPStorm is straight forward.
68+
69+
Configure the servers so we can debug a request made from the browser:
70+
![PHPStorm servers config](docs/IDE/PHPStorm/IDE_PHPSTORM_servers.png)
71+
72+
Configure the CLI so we can run the tests:
73+
![PHPStorm CLI config](docs/IDE/PHPStorm/IDE_PHPSTORM_cli_interpreter.png)
74+
75+
Configure the test run itself:
76+
![PHPStorm tests config](docs/IDE/PHPStorm/IDE_PHPSTORM_tests_run.png)
77+
6478
[1]: https://symfony.com/doc/current/reference/requirements.html
6579
[2]: https://symfony.com/doc/current/cookbook/configuration/web_server_configuration.html

bin/lib/docker.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
function isContainerRunning(string $containerName): bool
13+
{
14+
if (
15+
isContainerExists($containerName)
16+
&& 'true' === trim(shell_exec("docker inspect -f '{{.State.Running}}' $containerName 2>/dev/null"))
17+
) {
18+
return true;
19+
}
20+
21+
return false;
22+
}
23+
24+
function isContainerStopped(string $containerName): bool
25+
{
26+
if (
27+
isContainerExists($containerName)
28+
&& 'false' === trim(shell_exec("docker inspect -f '{{.State.Running}}' $containerName 2>/dev/null"))
29+
) {
30+
return true;
31+
}
32+
33+
return false;
34+
}
35+
36+
function isContainerExists(string $containerName): bool
37+
{
38+
if ('' === shell_exec("docker ps -a | grep $containerName")) {
39+
return false;
40+
}
41+
42+
return true;
43+
}

bin/run

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#!/usr/bin/env php
2+
<?php
3+
/**
4+
* This script allows us to start the container and related containers, and then run other commands against it.
5+
* With no arguments, the container will be started and kept alive for 1h
6+
* With arguments, if the container is running it will send the commands to it and leave it running
7+
* With arguments, if the container is NOT running it will start it, send the commands to it and terminate it
8+
*/
9+
10+
include_once('lib/docker.php');
11+
12+
$env = getenv('ENV') ?: 'dev';
13+
$argumentList = array_slice($argv, 1);
14+
$rootDir = __DIR__ . '/..';
15+
$returnVal = 0;
16+
$containerWasRunning = false;
17+
$detached = '';
18+
19+
$containerName = 'app.sfn.' . $env;
20+
$yamlFile = "$rootDir/build/container/$env/docker-compose.yml";
21+
$downCommand = "docker-compose -f $yamlFile down -t 0"; // we use the "-t 0" so that we don't have a timeout of 10s
22+
23+
if (empty($argumentList) && isContainerRunning($containerName)) {
24+
exit($returnVal);
25+
}
26+
27+
if (empty($argumentList)) {
28+
// If no command is given to execute, just keep the container running in the background for 1h
29+
$detached = '-d';
30+
$guestCommand = "sleep 3600";
31+
} else {
32+
$guestCommand = implode(' ', $argumentList);
33+
}
34+
35+
// We wrap the $guestCommand in /bin/sh so that we get the full output if it breaks, ie with a segfault
36+
$hostCommand = "docker-compose -f $yamlFile run $detached -u $(id -u):$(id -g) --name $containerName app /bin/sh -c '$guestCommand'";
37+
38+
if (!$detached) {
39+
// We wrap the host command in bash trap so that we can trap a signal and destroy the stopped containers
40+
// We only wrap it if it's not detached, otherwise detaching will make the container stop immediately
41+
$hostCommand = "bash -c \"trap 'echo Trapped exit! Exit code: $? \n $downCommand' SIGINT SIGTERM ERR; $hostCommand\"";
42+
}
43+
44+
if (isContainerRunning($containerName)) {
45+
$containerWasRunning = true;
46+
system("docker exec -it $containerName $guestCommand", $returnVal);
47+
} else {
48+
system($hostCommand, $returnVal);
49+
}
50+
51+
if (!$detached && !$containerWasRunning && isContainerExists($containerName)) {
52+
system($downCommand);
53+
}
54+
55+
exit($returnVal);

bin/stop

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/usr/bin/env php
2+
<?php
3+
4+
include_once('lib/docker.php');
5+
6+
$env = getenv('ENV') ?: 'dev';
7+
$rootDir = __DIR__ . '/..';
8+
$returnVal = 0;
9+
10+
$containerName = 'app.sfn.' . $env;
11+
$yamlFile = "$rootDir/build/container/$env/docker-compose.yml";
12+
13+
// we use the "-t 0" so that we don't have a timeout of 10s
14+
$downCommand = "docker-compose -f $yamlFile down -t 0";
15+
16+
if (isContainerExists($containerName)) {
17+
system($downCommand, $returnVal);
18+
}
19+
20+
exit($returnVal);

build/container/app.base.dockerfile

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
FROM php:7.1-alpine
2+
3+
# `apk --update` updates indexes before installing
4+
# `apk --no-cache` doesn't put stuff in the cache, so we don't need to remove it at the end
5+
# `apk --virtual` lets us uninstall temporary dependencies in one go at the end
6+
RUN apk --update add --no-cache --virtual build-dependencies autoconf g++ && \
7+
apk add --no-cache make zlib-dev curl && \
8+
mkdir -p /tmp/pear/cache && \
9+
docker-php-ext-configure pcntl && \
10+
docker-php-ext-install pcntl && \
11+
docker-php-ext-configure bcmath && \
12+
docker-php-ext-install bcmath && \
13+
docker-php-ext-configure zip && \
14+
docker-php-ext-install zip && \
15+
curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin --filename=composer && \
16+
apk del build-dependencies && \
17+
rm -rf /var/cache/apk/* && \
18+
pecl clear-cache

build/container/dev/app.dockerfile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
FROM hgraca/explicit-architecture:app.sfn.prd
2+
3+
ENV ENV="dev"
4+
5+
RUN apk --update add --no-cache --virtual build-dependencies autoconf g++ && \
6+
pecl install xdebug-2.6.0 && \
7+
apk del build-dependencies && \
8+
rm -rf /var/cache/apk/* && \
9+
pecl clear-cache
10+
11+
COPY build/container/dev/xdebug.ini /usr/local/etc/php/conf.d/xdebug.ini
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
version: '3'
2+
3+
services:
4+
app:
5+
image: hgraca/explicit-architecture:app.sfn.dev
6+
container_name: app.sfn.dev
7+
working_dir: /opt/app
8+
build:
9+
context: ../../../
10+
dockerfile: ./build/container/dev/app.dockerfile
11+
tty: true # fix for symfony saying: "proc_open(/dev/tty): failed to open stream: No such device or address"
12+
ports:
13+
- '80:8000' # This way we can run the application locally at http://localhost
14+
volumes:
15+
- ../../../:/opt/app
16+
- ~/.composer:/.composer # so we can use the host composer cache
17+
- ./php.ini:/usr/local/etc/php/php.ini # so we can easily change php config
18+
- ./xdebug.ini:/usr/local/etc/php/conf.d/xdebug.ini # so we can easily change xdebug config
19+
environment:
20+
PHP_IDE_CONFIG: 'serverName=docker' # This is needed to debug from CLI (ie. while running tests)
21+
ENV: 'dev'
22+
23+
###> symfony/framework-bundle ###
24+
APP_ENV: 'dev'
25+
APP_DEBUG: 1
26+
APP_SECRET: '67d829bf61dc5f87a73fd814e2c9f629'
27+
###< symfony/framework-bundle ###
28+
29+
###> doctrine/doctrine-bundle ###
30+
# Format described at http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
31+
# For a sqlite database, use: "sqlite:///%kernel.project_dir%/var/data.db"
32+
# Set "serverVersion" to your server version to avoid edge-case exceptions and extra database calls
33+
DATABASE_URL: 'sqlite:///%kernel.project_dir%/var/data/blog.sqlite'
34+
###< doctrine/doctrine-bundle ###
35+
36+
###> symfony/swiftmailer-bundle ###
37+
# For Gmail as a transport, use: "gmail://username:password@localhost"
38+
# For a generic SMTP server, use: "smtp://localhost:25?encryption=&auth_mode="
39+
# Delivery is disabled by default via "null://localhost"
40+
MAILER_URL: 'null://localhost'
41+
###< symfony/swiftmailer-bundle ###

build/container/dev/php.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
memory_limit = 1024M

build/container/dev/xdebug.ini

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
zend_extension = xdebug.so
2+
3+
xdebug.var_display_max_depth = 5
4+
xdebug.var_display_max_data = 250
5+
xdebug.var_display_max_children = 5
6+
xdebug.max_nesting_level = 250
7+
xdebug.cli_color = 1
8+
xdebug.show_exception_trace = 0
9+
xdebug.dump_globals = 1
10+
xdebug.dump_once = 1
11+
xdebug.show_local_vars = 1
12+
13+
xdebug.remote_autostart = 1 ; If 0, CLI debug will not work. If 1, always try to start a remote debugging session and connect to a client, regardless of the GET/POST/COOKIE variable being present.
14+
xdebug.remote_enable = 1 ; trigger: cookie:XDEBUG_SESSION_START=PHPSTORM
15+
xdebug.remote_host = '172.17.0.1' ; Host where the debug client is running, you can either use a host name, IP address, or 'unix:///path/to/sock' for a Unix domain socket. This setting is ignored if xdebug.remote_connect_back is enabled.
16+
xdebug.remote_connect_back = 0 ; If enabled, the xdebug.remote_host setting is ignored and Xdebug will try to connect to the client that made the HTTP request. This setting does not apply for debugging through the CLI
17+
xdebug.remote_port = 9000
18+
xdebug.remote_handler = dbgp
19+
xdebug.remote_mode = req
20+
21+
xdebug.profiler_enable = 0
22+
xdebug.profiler_enable_trigger = 1 ; trigger: cookie:XDEBUG_PROFILE => 1
23+
xdebug.profiler_output_dir = '/tmp'
24+
xdebug.profiler_output_name = "%H.profiler"
25+
26+
xdebug.auto_trace = 0
27+
xdebug.trace_enable_trigger = 1 ; trigger: cookie:XDEBUG_TRACE => 1
28+
xdebug.trace_options = 0
29+
xdebug.trace_format = 2
30+
xdebug.trace_output_dir = '/tmp'
31+
xdebug.trace_output_name = "%H.trace"
32+
xdebug.collect_params = 4
33+
xdebug.collect_return = 1
34+
35+
xdebug.idekey = "PHPSTORM"

build/container/docker-compose.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
version: '3.2'
2+
3+
services:
4+
app:
5+
image: hgraca/explicit-architecture:app.sfn.base
6+
container_name: app.sfn.base
7+
working_dir: /opt/app
8+
build:
9+
context: ../../
10+
dockerfile: ./build/container/app.base.dockerfile

0 commit comments

Comments
 (0)