├── source ├── css │ └── .gitkeep ├── favicon.ico ├── images │ ├── me.jpeg │ ├── me-small.jpeg │ ├── me_300px.jpeg │ └── blog │ │ └── software │ │ ├── tunnel.jpg │ │ ├── locked-door.jpg │ │ ├── smoke-and-ashes.jpg │ │ ├── composer-in-docker.png │ │ ├── traefik-services.png │ │ ├── traefik-architecture.png │ │ ├── traefik-header-image.png │ │ └── empowered-by-gnu.svg ├── design │ ├── palette.jpg │ └── palette-blurred.jpg ├── 404.html ├── _posts │ ├── draft-build-pipeline.md │ ├── draft-little-pianos.md │ ├── draft-backup-your-website-to-dropbox.md │ ├── draft-poor-mans-cron-jobs-in-docker-swarm.md │ ├── draft-pushing-docker-images-from-the-ci.md │ ├── crafting-videos-with-make-ffmpeg-and-awk.md │ ├── draft-music-tribute-to-rollo.md │ ├── draft-system-testing-with-behat-and-selenium-in-docker.md │ ├── draft-make-manifesto.md │ ├── draft-travis-folding-and-timing-with-make.md │ ├── draft-distributed-file-systems-with-glusterfs.md │ ├── draft-combining-unit-and-system-test-coverage.md │ ├── draft-building-docker-images-with-make.md │ ├── draft-docker-best-practices.md │ ├── draft-ev-car-start.md │ ├── draft-splitting-your-in-a-front-end-and-back-end-containers.md │ ├── draft-running-wordpress-in-docker.md │ ├── draft-r380-gearbox-short-bellhousing-conversion.md │ ├── draft-dealing-with-unavailable-services-on-application-start-up.md │ ├── draft-tools-from-the-golden-years-an-introduction-to-make.md │ ├── draft-testing-containerized-web-applications-with-docker-part-1-smoke-tests.md │ ├── draft-history-of-the-electric-discovery.md │ ├── 2017-06-25-accessing-your-docker-app-via-a-domain-name-using-traefik.md │ ├── 2017-06-28-running-cli-tools-in-docker-part-1-composer.md │ ├── 2017-12-31-truly-immutable-deployments-with-docker-or-kubernetes.md │ ├── draft-car-hot-dip-galvanizing.md │ └── 2018-02-02-deploying-dockerized-applications-via-an-ssh-tunnel.md ├── robots.txt ├── projects │ ├── index.md │ ├── electric-discovery │ │ └── component-overview.md │ └── electric-discovery.md ├── blog │ ├── tags.html │ ├── categories.html │ ├── tags │ │ ├── tag.xml │ │ └── tag.html │ └── categories │ │ ├── category.xml │ │ └── category.html ├── sitemap.xml ├── atom.xml ├── about.html ├── blog.html ├── scss │ ├── modules │ │ └── _colors.scss │ ├── main.scss │ └── normalize.scss ├── public-appearances.html ├── work.html ├── _layouts │ └── default.html ├── _views │ └── post.html ├── index.html └── expertise.html ├── app ├── config │ ├── sculpin_site_dev.yml │ ├── sculpin_kernel.yml │ └── sculpin_site.yml └── SculpinKernel.php ├── bin ├── new-post ├── generate-pygments-css └── new-post.php ├── composer ├── docker ├── sculpin │ ├── xdebug.ini │ └── Dockerfile ├── app │ ├── Dockerfile │ └── config │ │ └── conf.d │ │ └── default.conf └── sass │ └── Dockerfile ├── .gitignore ├── src └── SculpinCodeBlockBundle │ ├── SculpinCodeBlockBundle.php │ ├── DependencyInjection │ └── SculpinCodeBlockExtension.php │ ├── Resources │ └── config │ │ └── services.xml │ └── ConvertListener.php ├── env ├── ci │ └── docker-compose.yml ├── prod │ ├── traefik.toml │ └── docker-compose.yml └── default │ └── docker-compose.yml ├── README.md ├── .travis.yml ├── composer.json ├── make └── travis.mk ├── tests ├── smoke-test.sh └── validate-html.sh ├── docker-compose.yml ├── component-manager.php └── Makefile /source/css/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/config/sculpin_site_dev.yml: -------------------------------------------------------------------------------- 1 | url: http://lucasvanlierop.nl.localhost 2 | -------------------------------------------------------------------------------- /bin/new-post: -------------------------------------------------------------------------------- 1 | docker-compose run --rm sculpin php bin/new-post.php "$@" 2 | -------------------------------------------------------------------------------- /composer: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | docker-compose run --rm sculpin composer "$@" 3 | -------------------------------------------------------------------------------- /source/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucasvanlierop/website/HEAD/source/favicon.ico -------------------------------------------------------------------------------- /source/images/me.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucasvanlierop/website/HEAD/source/images/me.jpeg -------------------------------------------------------------------------------- /source/design/palette.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucasvanlierop/website/HEAD/source/design/palette.jpg -------------------------------------------------------------------------------- /source/images/me-small.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucasvanlierop/website/HEAD/source/images/me-small.jpeg -------------------------------------------------------------------------------- /source/images/me_300px.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucasvanlierop/website/HEAD/source/images/me_300px.jpeg -------------------------------------------------------------------------------- /app/config/sculpin_kernel.yml: -------------------------------------------------------------------------------- 1 | sculpin_content_types: 2 | posts: 3 | permalink: blog/:year/:month/:day/:filename/ 4 | -------------------------------------------------------------------------------- /source/404.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | permalink: none 4 | title: Four o four 5 | --- 6 | 7 |

Page not found

8 | -------------------------------------------------------------------------------- /source/design/palette-blurred.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucasvanlierop/website/HEAD/source/design/palette-blurred.jpg -------------------------------------------------------------------------------- /source/images/blog/software/tunnel.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucasvanlierop/website/HEAD/source/images/blog/software/tunnel.jpg -------------------------------------------------------------------------------- /docker/sculpin/xdebug.ini: -------------------------------------------------------------------------------- 1 | zend_extension=xdebug.so 2 | xdebug.remote_enable=1 3 | xdebug.remote_autostart=1 4 | xdebug.max_nesting_level=1000 5 | -------------------------------------------------------------------------------- /source/images/blog/software/locked-door.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucasvanlierop/website/HEAD/source/images/blog/software/locked-door.jpg -------------------------------------------------------------------------------- /source/images/blog/software/smoke-and-ashes.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucasvanlierop/website/HEAD/source/images/blog/software/smoke-and-ashes.jpg -------------------------------------------------------------------------------- /bin/generate-pygments-css: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | pygmentize \ 3 | -f html \ 4 | -S monokai \ 5 | -a .highlight \ 6 | > /app/source/css/pygments.css 7 | -------------------------------------------------------------------------------- /source/images/blog/software/composer-in-docker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucasvanlierop/website/HEAD/source/images/blog/software/composer-in-docker.png -------------------------------------------------------------------------------- /source/images/blog/software/traefik-services.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucasvanlierop/website/HEAD/source/images/blog/software/traefik-services.png -------------------------------------------------------------------------------- /source/images/blog/software/traefik-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucasvanlierop/website/HEAD/source/images/blog/software/traefik-architecture.png -------------------------------------------------------------------------------- /source/images/blog/software/traefik-header-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucasvanlierop/website/HEAD/source/images/blog/software/traefik-header-image.png -------------------------------------------------------------------------------- /source/_posts/draft-build-pipeline.md: -------------------------------------------------------------------------------- 1 | --- 2 | draft: true 3 | title: Setting up a build pipeline 4 | categories: 5 | - software-development 6 | --- 7 | Todo 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .built 2 | /source/css/* 3 | !/source/css/.gitkeep 4 | /.sculpin/ 5 | /app/cache/* 6 | /app/logs/* 7 | /output_*/ 8 | /source/components/ 9 | /vendor/ 10 | -------------------------------------------------------------------------------- /source/robots.txt: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: none 3 | --- 4 | 5 | User-agent: * 6 | Disallow: /blog/tags/ 7 | Disallow: /blog/categories/ 8 | 9 | Sitemap: {{ site.url }}/sitemap.xml 10 | -------------------------------------------------------------------------------- /source/_posts/draft-little-pianos.md: -------------------------------------------------------------------------------- 1 | --- 2 | draft: true 3 | title: My favorite songs with little piano parts 4 | categories: 5 | - music 6 | tags: 7 | - music 8 | --- 9 | 10 | -------------------------------------------------------------------------------- /source/_posts/draft-backup-your-website-to-dropbox.md: -------------------------------------------------------------------------------- 1 | --- 2 | draft: true 3 | layout: default 4 | title: Backup to Dropbox with clone 5 | categories: 6 | - software-development 7 | --- 8 | Todo 9 | -------------------------------------------------------------------------------- /source/_posts/draft-poor-mans-cron-jobs-in-docker-swarm.md: -------------------------------------------------------------------------------- 1 | --- 2 | draft: true 3 | layout: default 4 | title: Poor mans cron jobs in Docker Swarm 5 | categories: 6 | - software-development 7 | --- 8 | Todo 9 | -------------------------------------------------------------------------------- /docker/app/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:1.11-alpine 2 | 3 | COPY docker/app/config/conf.d/default.conf /etc/nginx/conf.d/default.conf 4 | 5 | RUN rm -rf /usr/share/nginx/html 6 | COPY output_prod /usr/share/nginx/html 7 | 8 | -------------------------------------------------------------------------------- /source/_posts/draft-pushing-docker-images-from-the-ci.md: -------------------------------------------------------------------------------- 1 | --- 2 | draft: true 3 | layout: default 4 | title: Pushing Docker Images From The CI 5 | categories: 6 | - software-development 7 | --- 8 | Todo 9 | 10 | -------------------------------------------------------------------------------- /source/projects/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | draft: true 3 | layout: "default" 4 | name: "Projects I work on" 5 | 6 | --- 7 | 8 | 9 | - [The conversion of a Land Rover Discovery to electric](/projects/electric-discovery) 10 | -------------------------------------------------------------------------------- /source/_posts/crafting-videos-with-make-ffmpeg-and-awk.md: -------------------------------------------------------------------------------- 1 | --- 2 | draft: true 3 | title: Crafting videos with make, ffmpeg and awk 4 | categories: 5 | software-development 6 | tags: 7 | - video 8 | - make 9 | --- 10 | -------------------------------------------------------------------------------- /source/_posts/draft-music-tribute-to-rollo.md: -------------------------------------------------------------------------------- 1 | --- 2 | draft: true 3 | title: A tribute to Rollo Armstrong, master producer 4 | categories: 5 | - music 6 | tags: 7 | - vinyl 8 | - house music 9 | --- 10 | Todo 11 | 12 | -------------------------------------------------------------------------------- /src/SculpinCodeBlockBundle/SculpinCodeBlockBundle.php: -------------------------------------------------------------------------------- 1 | Tags 8 | 9 | 14 | -------------------------------------------------------------------------------- /source/_posts/draft-system-testing-with-behat-and-selenium-in-docker.md: -------------------------------------------------------------------------------- 1 | --- 2 | draft: true 3 | title: System testing with Behat and Selenium in Docker 4 | categories: 5 | - software-development 6 | tags: 7 | - system-testing 8 | - docker 9 | - behat 10 | - selenium 11 | --- 12 | Todo 13 | -------------------------------------------------------------------------------- /source/blog/categories.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Categories 4 | use: 5 | - posts_categories 6 | --- 7 |

Categories

8 | 9 | 16 | -------------------------------------------------------------------------------- /docker/sass/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:2.3-alpine 2 | 3 | # Install Sass and dependencies 4 | RUN apk add \ 5 | --update \ 6 | --no-cache \ 7 | --virtual build-dependencies \ 8 | ruby-dev \ 9 | libffi-dev \ 10 | build-base \ 11 | && gem install \ 12 | sass \ 13 | && apk del build-dependencies 14 | 15 | ENTRYPOINT ["sass"] 16 | -------------------------------------------------------------------------------- /app/SculpinKernel.php: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | {{ site.url }} 9 | {{ site.calculated_date | date('Y-m-d') }} 10 | daily 11 | 0.8 12 | 13 | {% for post in data.posts %} 14 | 15 | {{ site.url }}{{ post.url }} 16 | {{ post.date|date('c') }} 17 | weekly 18 | 1.0 19 | 20 | {% endfor %} 21 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/lucasvanlierop/website.svg?branch=master)](https://travis-ci.org/lucasvanlierop/website) 2 | 3 | #Website about my freelance developer activities. 4 | 5 | This site uses [Sass](http://sass-lang.com/) for css and [Sculpin](https://sculpin.io/) for static site generation 6 | and runs in [Docker](docker.io). Images are pushed to [Docker Hub](https://hub.docker.com/r/lucasvanlierop/website/) 7 | 8 | To develop: 9 | 10 | - export your user id as `$HOST_UID` 11 | 12 | - export your group id as `$HOST_GID` 13 | 14 | - run: `docker-compose up` 15 | 16 | To build and test run: `make test` 17 | 18 | To just build run: `make` 19 | -------------------------------------------------------------------------------- /env/prod/traefik.toml: -------------------------------------------------------------------------------- 1 | debug = false 2 | checkNewVersion = true 3 | logLevel = "ERROR" 4 | defaultEntryPoints = ["https","http"] 5 | 6 | [entryPoints] 7 | [entryPoints.http] 8 | address = ":80" 9 | [entryPoints.http.redirect] 10 | entryPoint = "https" 11 | [entryPoints.https] 12 | address = ":443" 13 | [entryPoints.https.tls] 14 | 15 | [retry] 16 | 17 | [docker] 18 | endpoint = "unix:///var/run/docker.sock" 19 | domain = "lucasvanlierop.nl" 20 | watch = true 21 | exposedbydefault = false 22 | 23 | [acme] 24 | email = "me@lucasvanlierop.nl" 25 | storage = "acme.json" 26 | OnHostRule = true 27 | entryPoint = "https" 28 | [acme.httpChallenge] 29 | entryPoint = "http" 30 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | env: 2 | global: 3 | - HOST_UID=$UID 4 | - HOST_GID=$GID 5 | - TRAVIS=on 6 | 7 | services: 8 | - docker 9 | 10 | before_script: 11 | - export -f travis_nanoseconds 12 | - export -f travis_fold 13 | - export -f travis_time_start 14 | - export -f travis_time_finish 15 | 16 | script: 17 | - make test 18 | 19 | after_failure: 20 | - docker-compose logs 21 | 22 | after_success: 23 | - if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then docker login --username=$DOCKER_USERNAME --password=$DOCKER_PASSWORD && docker push lucasvanlierop/website:latest; fi 24 | 25 | branches: 26 | only: 27 | - master 28 | 29 | cache: 30 | directories: 31 | - ~/.composer/cache 32 | -------------------------------------------------------------------------------- /env/prod/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | # Should be moved to a separate repo 5 | ingress: 6 | image: traefik:1.5-alpine 7 | ports: 8 | - 80:80 9 | - 443:443 10 | volumes: 11 | - /var/run/docker.sock:/var/run/docker.sock 12 | - /opt/traefik/traefik.toml:/traefik.toml 13 | - /opt/traefik/acme.json:/acme.json 14 | 15 | app: 16 | image: lucasvanlierop/website 17 | labels: 18 | - "traefik.enable=true" 19 | - "traefik.backend=lucasvanlierop-web" 20 | - "traefik.frontend.rule=Host:lucasvanlierop.nl,www.lucasvanlierop.nl" 21 | - "traefik.port=80" 22 | - "traefik.docker.network=lucasvanlierop-website_default" 23 | -------------------------------------------------------------------------------- /bin/new-post.php: -------------------------------------------------------------------------------- 1 | load('services.xml'); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/SculpinCodeBlockBundle/Resources/config/services.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lucasvanlierop/website", 3 | "description": "Website about my freelance work + my blog", 4 | "type": "project", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Lucas van Lierop", 9 | "email": "me@lucasvanlierop.nl" 10 | } 11 | ], 12 | "minimum-stability": "dev", 13 | "prefer-stable": true, 14 | "require": { 15 | "sculpin/sculpin": "^2.1@dev", 16 | "ramsey/twig-codeblock": "~1.0" 17 | }, 18 | "scripts": { 19 | "post-install-cmd": [ 20 | "ComponentManager\\ComponentManager::postComposerInstall" 21 | ] 22 | }, 23 | "autoload": { 24 | "classmap": ["component-manager.php"], 25 | "psr-4": { 26 | "App\\": "src" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /make/travis.mk: -------------------------------------------------------------------------------- 1 | ifeq ($(TRAVIS),on) 2 | TARGET_TIMER_ID_FILE=/tmp/timer-id-$(shell echo "$(TARGET)" | sed "s/[^[:alpha:]]//g") 3 | TARGET_TIME_FILE=/tmp/time-$(shell echo "$(TARGET)" | sed "s/[^[:alpha:]]//g") 4 | TRAVIS_PRINTABLE_MARKER_NAME=$(shell echo "$(TARGET)" | sed "s/[^[:alpha:]]/-/g") 5 | TARGET_MARKER_START = travis_fold start "$(TRAVIS_PRINTABLE_MARKER_NAME)" && \ 6 | travis_time_start && \ 7 | echo $$travis_timer_id > $(TARGET_TIMER_ID_FILE) && \ 8 | echo $$travis_start_time > $(TARGET_TIME_FILE) 9 | TARGET_MARKER_END = \ 10 | travis_timer_id=$$(cat $(TARGET_TIMER_ID_FILE)) \ 11 | travis_start_time=$$(cat $(TARGET_TIME_FILE)) \ 12 | travis_time_finish && \ 13 | travis_fold end "$(TRAVIS_PRINTABLE_MARKER_NAME)" 14 | else 15 | TARGET_MARKER_START = @echo "starting: $(TARGET)" 16 | TARGET_MARKER_END = @echo "ended $(TARGET)" 17 | endif 18 | -------------------------------------------------------------------------------- /env/default/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | app: 5 | restart: on-failure 6 | container_name: lucasvanlierop-website 7 | image: lucasvanlierop/website 8 | 9 | sass: 10 | restart: on-failure 11 | container_name: lucasvanlierop-website-sass 12 | image: lucasvanlierop/website-sass 13 | build: 14 | context: ../../ 15 | dockerfile: docker/sass/Dockerfile 16 | command: --watch /app/source/scss:/app/source/css 17 | user: $HOST_UID:$HOST_GID 18 | volumes: 19 | - ../../:/app 20 | 21 | sculpin: 22 | build: 23 | context: ../../docker/sculpin 24 | restart: on-failure 25 | container_name: lucasvanlierop-website-sculpin 26 | user: $HOST_UID:$HOST_GID 27 | working_dir: /app 28 | environment: 29 | HOME: /home 30 | volumes: 31 | - ../../:/app 32 | - ~/.composer:/home/.composer 33 | -------------------------------------------------------------------------------- /source/_posts/draft-ev-car-start.md: -------------------------------------------------------------------------------- 1 | --- 2 | draft: true 3 | title: Starting the EV conversion project for the Land Rover 4 | tags: 5 | - land rover 6 | - ev conversion 7 | - project-discover-e 8 | categories: 9 | - cars 10 | --- 11 | Todo 12 | 13 | ## Inspirators: 14 | 15 | - [Jehu Garcia](https://twitter.com/jag35), because of all the 'driving in sunny California' feelgood moments he gave me while watching his 16 | [Volkswagen T1 'Samba' conversion series](https://www.youtube.com/watch?v=5UeVsHmKAFA&list=PL2l32K1w0jp-m04Jl-3PHmtapevOuCeIp). 17 | - [Robert Llewellyn](https://twitter.com/bobbyllew), because of his 18 | [vision on the future of electric cars](https://www.youtube.com/watch?v=6q7_xN0ilag) 19 | - [Lars Rengersen](https://twitter.com/oudevolvo), because of doing 20 | [an actual EV conversion in the Netherlands compliant with Dutch law](https://www.oudevolvo.nl/ev-combi/) 21 | 22 | 23 | -------------------------------------------------------------------------------- /tests/smoke-test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | assert_page_contains() { 6 | local test_url="$1"; 7 | local expected_content="$2" 8 | local test_output 9 | 10 | declare -i timeout=5 11 | 12 | while ! test_output=`docker-compose -f env/ci/docker-compose.yml run --rm --entrypoint=curl sculpin --silent --fail ${test_url}`; 13 | do sleep 0.1; 14 | done 15 | 16 | if [[ -z $(echo "${test_output}" | grep "${expected_content}") ]]; then 17 | echo "Failed asserting that '${test_output}' contains '$expected_content'" && exit 1; 18 | fi 19 | } 20 | 21 | assert_page_contains 'http://app' 'freelance software developer' 22 | assert_page_contains 'http://app/about/' 'About Lucas van Lierop' 23 | assert_page_contains 'http://app/expertise/' 'My expertise' 24 | assert_page_contains 'http://app/public-appearances/' 'Public appearances' 25 | assert_page_contains 'http://app/work/' 'This is some of the work I did at my former employers.' 26 | -------------------------------------------------------------------------------- /tests/validate-html.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Pull image first so no docker output ends up in stderr later on. 4 | docker pull magnetikonline/html5validator:latest 5 | 6 | assert_valid_html() { 7 | local url=$1 8 | local validation_errors 9 | 10 | 11 | # Note that in order to capture the commands stderr its' redirected to stdout 12 | # While stdout itself is redirected to a third filedescriptor which is outputted afterwards. 13 | { validation_errors=$( 14 | docker run \ 15 | --rm \ 16 | --name=html-validator \ 17 | --network=container:lucasvanlierop-website-ci \ 18 | magnetikonline/html5validator \ 19 | java -jar /root/build/validator.nu/vnu.jar $url \ 20 | 2>&1 1>&3- 21 | ) ;} 3>&1 22 | 23 | # Catch warnings too 24 | if [[ ! -z "${validation_errors}" ]]; then 25 | echo "HTML NOT VALID ${validation_errors}" 26 | exit 1 27 | fi 28 | } 29 | 30 | assert_valid_html 'http://app' 31 | assert_valid_html 'http://app/about/' 32 | assert_valid_html 'http://app/expertise/' 33 | assert_valid_html 'http://app/public-appearances/' 34 | assert_valid_html 'http://app/work/' 35 | -------------------------------------------------------------------------------- /docker/sculpin/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7.1-alpine 2 | 3 | RUN apk update \ 4 | 5 | # Install init system for running tasks as pid 1 6 | && apk add --no-cache tini \ 7 | 8 | # Install Composer + Git + Zip so it can fetch from various sources 9 | && apk add \ 10 | --no-cache \ 11 | git \ 12 | zlib-dev \ 13 | && php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \ 14 | && php composer-setup.php \ 15 | && php -r "unlink('composer-setup.php');" \ 16 | && chmod +x composer.phar \ 17 | && mv composer.phar /usr/local/bin/composer \ 18 | 19 | # Install PHP Xdebug extension 20 | && apk add \ 21 | --no-cache \ 22 | --virtual .build-dependencies \ 23 | autoconf \ 24 | g++ \ 25 | make \ 26 | && pecl install xdebug \ 27 | && docker-php-ext-enable xdebug \ 28 | && apk del .build-dependencies 29 | 30 | COPY xdebug.ini ${PHP_INI_DIR}/conf.d/docker-php-ext-xdebug.ini 31 | 32 | RUN apk add \ 33 | --no-cache \ 34 | python 35 | 36 | RUN apk add \ 37 | --no-cache \ 38 | py-pygments 39 | 40 | # Cleanup 41 | # && rm -rf /var/cache/apk/* 42 | 43 | ENTRYPOINT ["/sbin/tini", "--"] 44 | 45 | -------------------------------------------------------------------------------- /source/atom.xml: -------------------------------------------------------------------------------- 1 | --- 2 | use: ["posts"] 3 | permalink: atom.xml 4 | --- 5 | 6 | 7 | <![CDATA[{{ site.title }}]]> 8 | 9 | 10 | {{ site.calculated_date | date('c') }} 11 | {{ site.url }}/ 12 | {% if site.author or site.email %} 13 | 14 | {% if site.author %}{% endif %} 15 | {% if site.email %}{% endif %} 16 | 17 | {% endif %} 18 | Sculpin 19 | {% for post in data.posts|slice(0, 10) %} 20 | 21 | <![CDATA[{{ post.title }}]]> 22 | 23 | {{ post.date|date('c') }} 24 | {{ site.url }}{{ post.url }} 25 | 26 | 27 | {% endfor %} 28 | 29 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | networks: 4 | traefik: 5 | external: 6 | name: traefik_webgateway 7 | 8 | services: 9 | app: 10 | extends: 11 | file: env/default/docker-compose.yml 12 | service: app 13 | volumes: 14 | - ./output_dev:/usr/share/nginx/html 15 | networks: 16 | default: 17 | traefik: 18 | labels: 19 | - "traefik.enable=true" 20 | - "traefik.backend=lucasvanlierop-web" 21 | - "traefik.frontend.rule=Host:lucasvanlierop.nl.localhost" 22 | - "traefik.port=80" 23 | - "traefik.docker.network=traefik_webgateway" 24 | 25 | sass: 26 | extends: 27 | file: env/default/docker-compose.yml 28 | service: sass 29 | 30 | sculpin: 31 | extends: 32 | file: env/default/docker-compose.yml 33 | service: sculpin 34 | environment: 35 | XDEBUG_CONFIG: "remote_host=${DOCKER_HOST_IP_OR_NAME}" 36 | PHP_IDE_CONFIG: "serverName=lucasvanlierop.nl.localhost" 37 | command: | 38 | sh -c ' \ 39 | composer install \ 40 | && bin/generate-pygments-css \ 41 | && rm -rf output_dev/* \ 42 | && vendor/bin/sculpin generate \ 43 | --watch 44 | ' 45 | -------------------------------------------------------------------------------- /source/blog/tags/tag.xml: -------------------------------------------------------------------------------- 1 | --- 2 | generator: [posts_tag_index] 3 | 4 | --- 5 | 6 | 7 | <![CDATA[{{ site.title }}]]> 8 | 9 | 10 | {{ site.calculated_date | date('c') }} 11 | {{ site.url }}/ 12 | {% if site.author or site.email %} 13 | 14 | {% if site.author %}{% endif %} 15 | {% if site.email %}{% endif %} 16 | 17 | {% endif %} 18 | Sculpin 19 | {% for post in page.tag_posts|slice(0, 10) %} 20 | 21 | <![CDATA[{{ post.title }}]]> 22 | 23 | {{ post.date|date('c') }} 24 | {{ site.url }}{{ post.url }} 25 | 26 | 27 | {% endfor %} 28 | 29 | -------------------------------------------------------------------------------- /source/blog/categories/category.xml: -------------------------------------------------------------------------------- 1 | --- 2 | generator: [posts_category_index] 3 | 4 | --- 5 | 6 | 7 | <![CDATA[{{ site.title }}]]> 8 | 9 | 10 | {{ site.calculated_date | date('c') }} 11 | {{ site.url }}/ 12 | {% if site.author or site.email %} 13 | 14 | {% if site.author %}{% endif %} 15 | {% if site.email %}{% endif %} 16 | 17 | {% endif %} 18 | Sculpin 19 | {% for post in page.category_posts|slice(0, 10) %} 20 | 21 | <![CDATA[{{ post.title }}]]> 22 | 23 | {{ post.date|date('c') }} 24 | {{ site.url }}{{ post.url }} 25 | 26 | 27 | {% endfor %} 28 | 29 | -------------------------------------------------------------------------------- /component-manager.php: -------------------------------------------------------------------------------- 1 | getComposer()->getConfig(); 12 | $componentDir = $config->get('component-dir'); 13 | $components = $config->get('components'); 14 | $vendorDir = $config->get('vendor-dir'); 15 | 16 | // if either configuration is empty it's a noop 17 | if (empty($componentDir) || empty($components)) { 18 | return; 19 | } 20 | 21 | $componentDir = __DIR__ . 'component-manager.php/' . $componentDir; 22 | 23 | if (!is_dir($componentDir)) { 24 | mkdir($componentDir); 25 | } 26 | 27 | $filesystem = new Filesystem(); 28 | 29 | foreach ($components as $component) { 30 | $componentSource = $vendorDir .'/'. $component; 31 | 32 | if (!is_dir($componentSource)) { 33 | continue; 34 | } 35 | 36 | $filesystem->mirror($componentSource, $componentDir .'/'. basename($component)); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /source/about.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: "default" 3 | name: "About Lucas van Lierop" 4 | --- 5 | 6 |
7 |

8 | {{ page.name }} 9 |

10 | 11 | this is me 12 | 13 |

14 | I'm Lucas, a freelance software developer, working professionaly since 2004. 15 | My goal is to build well designed, well tested and properly documented applications that exceed your expectations. 16 |

17 | 18 |

19 | I work mostly in PHP and Javascript, but I'm learning other languages as well. For more info check my expertise. 20 |

21 | 22 |

23 | Besides being an engineer I'm also organizer of DomCode, a non-profit foundation with 1200+ 24 | members that organizes events like the annual DomCode conference and 25 | monthly DomCode meetups where (inter)national speakers share their knowledge on 26 | software development. 27 | Since I value knowledge sharing a lot I became an occasional speaker myself. 28 |

29 | 30 |

31 | I love to work with other freelancers or companies. Contact me if you are interested. 32 |

33 |
34 | -------------------------------------------------------------------------------- /source/_posts/draft-splitting-your-in-a-front-end-and-back-end-containers.md: -------------------------------------------------------------------------------- 1 | --- 2 | draft: true 3 | title: Spliting your app in a back and front end service 4 | tags: 5 | - nginx 6 | - docker 7 | - php 8 | categories: 9 | - software-development 10 | --- 11 | 12 | Splitting an application into separate backend and front end services has the following advantages: 13 | - Both services will be simpler since they have a single responsibility. 14 | - Both services can be scaled individually 15 | 16 | # Caveats 17 | 18 | # Resolving backend from the frontend 19 | 20 | While (at least in Docker Compose/Docker Swarm setups) services can be resolved by their name this doesn't always work out of the box. 21 | External domain names seem to have precedence over internal service names. 22 | Most of the time this is not a problem. 23 | Until the name of a service is an existing top level domain like [`app`](https://icannwiki.org/.app). 24 | Nginx will try to resolve `app` which results in a conflict. 25 | This can be resolved by telling Nginx to use the [internal Docker DNS](https://docs.docker.com/engine/userguide/networking/configure-dns/). 26 | 27 | An example is: 28 | 29 | 30 | 31 | Docker service names 32 | TODO check this ^ 33 | 34 | Make sure frontend knows how determine which backend to use. 35 | 36 | When nginx resolves it's upstream it doesn't use the Docker DNS by default. 37 | 38 | 39 | -------------------------------------------------------------------------------- /source/blog/tags/tag.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Tag Archive 4 | generator: [posts_tag_index, pagination] 5 | pagination: 6 | provider: page.tag_posts 7 | 8 | --- 9 | 10 | {% block head_meta %} 11 | 12 | 13 | {% endblock %} 14 | 15 | {% block title %}{{ page.title }} "{{ page.tag }}"{% endblock %} 16 | {% block content %} 17 | {% set year = '0' %} 18 |

"{{ page.tag }}"

19 | {% for post in page.pagination.items %} 20 | {% set this_year %}{{ post.date | date("Y") }}{% endset %} 21 | {% if year != this_year %} 22 | {% set month = '0' %} 23 | {% set year = this_year %} 24 | {% endif %} 25 | {% set this_month %}{{ post.date | date("F") }}{% endset %} 26 | {% if month != this_month %} 27 | {% set month = this_month %} 28 |

{{ month }} {{ year }}

29 | {% endif %} 30 |
31 |
32 | {{ post.title }} 33 | | 34 | {{ post.date | date("Y, F jS") }} 35 |
36 |
37 | {% endfor %} 38 | 39 |
40 | {% if page.pagination.previous_page or page.pagination.next_page %} 41 | 49 | {% endif %} 50 |
51 | {% endblock content %} 52 | -------------------------------------------------------------------------------- /source/blog.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Posts Archive 4 | generator: pagination 5 | use: 6 | - posts 7 | - posts_categories 8 | 9 | --- 10 | 11 | 16 | 17 |
18 | {% set year = '0' %} 19 |

Posts Archive

20 | {% for post in page.pagination.items %} 21 | {% set this_year %}{{ post.date | date("Y") }}{% endset %} 22 | {% if year != this_year %} 23 | {% set month = '0' %} 24 | {% set year = this_year %} 25 | {% endif %} 26 | {% set this_month %}{{ post.date | date("F") }}{% endset %} 27 | {% if month != this_month %} 28 | {% set month = this_month %} 29 |

{{ month }} {{ year }}

30 | {% endif %} 31 |
32 |

33 | 34 | {{ post.title }} 35 |

36 | 37 | {% if post.meta.tags %} 38 |

39 | {% for tag in post.meta.tags %} 40 | {{ tag }}{% if not loop.last %}, {% endif %} 41 | {% endfor %} 42 | | 43 | {{ post.date | date("Y, F jS") }} 44 |

45 | {% endif %} 46 |
47 | {% endfor %} 48 | 49 |
50 | {% if page.pagination.previous_page or page.pagination.next_page %} 51 | 59 | {% endif %} 60 |
61 |
62 | -------------------------------------------------------------------------------- /source/projects/electric-discovery/component-overview.md: -------------------------------------------------------------------------------- 1 | --- 2 | draft: true 3 | layout: "default" 4 | name: "Electric Land Rover Discovery project - Overview" 5 | 6 | --- 7 | 8 | As you can see a lot is still 'to be determined' (TBD). 9 | Once these have been decided they will be added to this page. 10 | 11 | ## Motor 12 | 13 | - Motor: Siemens 1PV5135-4WS14, custom conversion by [New Electric](http://www.newelectric.nl/) 14 | (not from the Azure Dynamics bankruptcy) 15 | - Coolant pump: TBD 16 | - Coolant pump controller: TBD 17 | - Coolant radiator: TBD 18 | 19 | ## Gearing 20 | 21 | - Shortened (a.k.a. 'stumpy') Land Rover R380 gearbox (short bellhousing) 22 | - Contra bellhousing 23 | - Custom made flywheel to motor coupler (a.k.a. 'puck'), by [New Electric](http://www.newelectric.nl/) 24 | - Standard clutch (might be lightened still) 25 | - [Heavy duty clutch kit from Ashcroft Transmissions](http://www.ashcroft-transmissions.co.uk/miscellaneous/clutches/130-tdi-clutch-kit.html) 26 | - Custom made bellhousing, [New Electric](http://www.newelectric.nl/) 27 | 28 | ## Controller 29 | 30 | - [SevCon Gen4 Size 8](http://www.sevcon.com/products/high-voltage-controllers/gen4-s8/) Controller 31 | - Coolant pump: TBD 32 | - Coolant pump controller: TBD 33 | - Coolant radiator: TBD 34 | electric-discovery 35 | 36 | ## Battery 37 | 38 | - Complete Tesla Model S 85 pack (16 modules), ~400V 39 | - Management system: TBD (Maybe reuse the the Tesla slave board with a custom controller) 40 | - Heater: TBD 41 | - Coolant pump: TBD 42 | - Coolant pump controller: TBD 43 | - Coolant radiator: TBD 44 | 45 | ## Charging 46 | 47 | - Charge port: TBD (most likely ChaDeMo) 48 | - AC Charger: TBD (maybe a 10 kW Tesla Charger?) 49 | - DC Charging controller: TBD 50 | 51 | ## Interior air conditioning 52 | 53 | - Compressor: TBD (probably an HV (300-400v) compressor) 54 | 55 | ## Interior heating 56 | - Heating: TBD: (probably a standard system with a PTC heater element) 57 | 58 | ## Ancillaries 59 | - Br ake Vacuum pump: TBD 60 | - Power assisted steering pump: TBD 61 | -------------------------------------------------------------------------------- /source/_posts/draft-running-wordpress-in-docker.md: -------------------------------------------------------------------------------- 1 | --- 2 | draft: true 3 | title: Running Wordpress in Docker 4 | categories: 5 | software-development 6 | tags: 7 | - testing 8 | - docker 9 | - docker-festival 10 | - wordpress 11 | --- 12 | 13 | Last year I took over maintainance and hosting for my wife's blog [Allihoppa.nl](http://allihoppa.nl) which happens to be a WordPress site. 14 | 15 | To have something to hold on to I decided to install and run WordPress the way I was used to with other web applications, namely: 16 | - Install vendor code with [Composer](https://getcomposer.org/) (check [this article](/blog/2017/06/28/running-cli-tools-in-docker-part-1-composer/) 17 | - Run it in a [Immutable Docker Container](/blog/2017/12/31/truly-immutable-deployments-with-docker-or-kubernetes/) 18 | That turned to be a bit different than what most WordPress people in my environment were used to. 19 | 20 | Things I ran into: 21 | - Installing WordPress Core/Themes/Plugins with Composer (only after I already resolved many of the problems I ran into I found that [Benjamin Eberlei had similar problems with his wife's blog](https://beberlei.de/2016/02/21/how_i_use_wordpress_with_git_and_composer.html)) 22 | - Accessing the website by domain 23 | - Preventing internal CLI calls (cron jobs, Akismet) from using the external domain/port 24 | - Set WP_HOME 25 | - Configuring different environments (using env vars) 26 | - Installing js vendors with Bower (https://github.com/allihoppa/allihoppa.nl/blob/master/docker/js-build/Dockerfile) 27 | - Upgrade db (https://github.com/allihoppa/allihoppa.nl/blob/master/bin/upgrade.php) 28 | - Db migrations (https://github.com/allihoppa/allihoppa.nl/commit/ddc8c00047e25fe39e6de3aac1de542a65f84cc4) 29 | - Test db connection (https://github.com/allihoppa/allihoppa.nl/blob/master/bin/test-db-connection.php) 30 | - WP CLI (https://github.com/allihoppa/allihoppa.nl/blob/master/wp) 31 | - debug mail (https://github.com/allihoppa/allihoppa.nl/commit/2a271899a1eb6eb1e8e1ecb6c47648d0c657a761) 32 | - Wait for db etc: ()https://github.com/allihoppa/allihoppa.nl/blob/master/bin/wait-for-db) 33 | - Testing with selenium chrome and Behat (WordHat?) 34 | - Running MySQL in docker (https://github.com/allihoppa/allihoppa.nl/blob/master/docker/service/mysql/backup) 35 | 36 | - Backup (remote rclone) 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /source/blog/categories/category.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Category Archive 4 | generator: [posts_category_index, pagination] 5 | pagination: 6 | provider: page.category_posts 7 | 8 | --- 9 | 10 | {% block head_meta %} 11 | 12 | 13 | {% endblock %} 14 | 15 | 16 | {% block title %}{{ page.title }} "{{ page.category }}"{% endblock %} 17 | {% block content %} 18 |
19 | {% set year = '0' %} 20 |

"{{ page.category }}"

21 | {% for post in page.pagination.items %} 22 | {% set this_year %}{{ post.date | date("Y") }}{% endset %} 23 | {% if year != this_year %} 24 | {% set month = '0' %} 25 | {% set year = this_year %} 26 | {% endif %} 27 | {% set this_month %}{{ post.date | date("F") }}{% endset %} 28 | {% if month != this_month %} 29 | {% set month = this_month %} 30 |

{{ month }} {{ year }}

31 | {% endif %} 32 |
33 |

34 | 35 | {{ post.title }} 36 |

37 | 38 | 39 | {% if post.meta.tags %} 40 |

41 | Tags: 42 | {% for tag in post.meta.tags %} 43 | {{ tag }}{% if not loop.last %}, {% endif %} 44 | {% endfor %} 45 | | 46 | {{ post.date | date("Y, F jS") }} 47 |

48 | {% endif %} 49 |
50 | {% endfor %} 51 | 52 |
53 | {% if page.pagination.previous_page or page.pagination.next_page %} 54 | 62 | {% endif %} 63 |
64 |
65 | {% endblock content %} 66 | 67 | -------------------------------------------------------------------------------- /source/_posts/draft-r380-gearbox-short-bellhousing-conversion.md: -------------------------------------------------------------------------------- 1 | --- 2 | draft: true 3 | title: Converting the Land Rover R380 Gearbox to a short bellhousing 4 | tags: 5 | - land rover 6 | - ev conversion 7 | categories: 8 | - cars 9 | --- 10 | https://youtu.be/H7CLT4PDA7g?t=212 11 | https://www.youtube.com/watch?v=ZZ9Khh_bFiE 12 | 13 | 14 | Conversion parts: 15 | 16 | * Ashcroft says: "Front Housing for the short bellhousing R380, Note we are only able to supply for the suffix K / L, 17 | the suffix J version is no longer available." 18 | 19 | Also: 20 | FTC4449 Torx M5*30 (http://www.paddockspares.com/ftc4449-screw.html) 21 | FRC7855 Strainer transmission oil http://www.paddockspares.com/catalogsearch/result/?q=frc7855 22 | 23 | ## Input shaft 24 | - [Seal - FTC5303G *Note this for a Suffix K!](http://www.paddockspares.com/ftc5303g-seal.html) 25 | - [Bearing - TZZ100190G *Note this is for a Suffix K! instead of the RTC6751 which is Suffix J!*)](http://www.paddockspares.com/bearing-11.html) 26 | - [Dowel flywheel to crankshaft - ERR1630](http://www.paddockspares.com/err1630-dowel-crank.html) 27 | - [Crankshaft bush - 8566L](http://www.paddockspares.com/8566l-crankshaft-bush-not-v8.html) 28 | 29 | ## Output shaft 30 | - [Seal - FTC500010G (supercedes FTC2383)](http://www.paddockspares.com/ftc500010g-seal-oil.html) 31 | - [TODO: Bearing](https://www.paddockspares.com/catalogsearch/result/?q=FTC2385G) 32 | - [Collar oil seal - FTC4021](http://www.paddockspares.com/ftc4021-collar-oil-seal.html) 33 | 34 | ## Layshaft 35 | - [Nut - EJP7738L](http://www.paddockspares.com/ejp7738l-nut-layshaft.html) 36 | 37 | ## Clutch 38 | - [Rod clip - FRC3327](http://www.paddockspares.com/frc3327-clip.html) 39 | 40 | ## Shifting 41 | - [Dust seal - FRC8507](http://www.paddockspares.com/frc8507-seal-dust.html) 42 | 43 | Vriespluggen Pag C 47 44 | 45 | 46 | ERR3920 Defender flywheel housing http://www.paddockspares.com/flywheel-housing.html 47 | ERR4722 http://www.paddockspares.com/housing-flywheel.html 48 | 602141 Dowel Flywheel http://www.paddockspares.com/602141-dowel-flywheel-hsg.html 49 | ETC7336 Defender 2.5 N/A Flywheel http://www.lrseries.com/shop/product/listing/6096/ETC7336-FLYWHEEL-HOUSING-2-5L-NA-DIESEL.html 50 | ERR1330 200Tdi old 51 | ERR3924 200Tdi new 52 | 53 | 200Tdi flywheel: https://youtu.be/D1NuD4smptU?t=108 54 | 55 | 56 | # Drain washer copper http://www.paddockspares.com/ftc4112-washer-copper.html 57 | -------------------------------------------------------------------------------- /source/scss/modules/_colors.scss: -------------------------------------------------------------------------------- 1 | $palette-light-grey: #9da9a9; 2 | $palette-grey: #626866; 3 | $palette-light-reddish-grey: #bdb9ae; 4 | $palette-reddish-grey: #998f8a; 5 | $palette-lime: #dcd881; 6 | $palette-citron: #e6d681; 7 | $palette-orange: #e9be52; 8 | $palette-mint: #ccd8c0; 9 | $palette-dark-mint: #7db5aa; 10 | $palette-medium-petrol: #78b5b7; 11 | $palette-petrol: #3d7a8d; 12 | $palette-salmon: #e68f81; 13 | $palette-rose: #d57485; 14 | 15 | .palette-bg-light-grey { 16 | background-color: $palette-light-grey; 17 | } 18 | 19 | .palette-light-grey { 20 | color: $palette-light-grey; 21 | } 22 | 23 | .palette-bg-grey { 24 | background-color: $palette-grey; 25 | color: white; 26 | } 27 | 28 | .palette-grey { 29 | color: $palette-grey; 30 | } 31 | 32 | .palette-bg-light-reddish-grey { 33 | background-color: $palette-light-reddish-grey; 34 | } 35 | 36 | .palette-light-reddish-grey { 37 | color: $palette-light-reddish-grey; 38 | } 39 | 40 | .palette-bg-reddish-grey { 41 | background-color: $palette-reddish-grey; 42 | } 43 | 44 | .palette-reddish-grey { 45 | color: $palette-reddish-grey; 46 | } 47 | 48 | .palette-bg-lime { 49 | background-color: $palette-lime; 50 | } 51 | 52 | .palette-lime { 53 | color: $palette-lime; 54 | } 55 | 56 | .palette-bg-citron { 57 | background-color: $palette-citron; 58 | } 59 | 60 | .palette-citron { 61 | color: $palette-citron; 62 | } 63 | 64 | .palette-bg-orange { 65 | background-color: $palette-orange; 66 | } 67 | 68 | .palette-orange { 69 | color: $palette-orange; 70 | } 71 | 72 | .palette-bg-mint { 73 | background-color: $palette-mint; 74 | } 75 | 76 | .palette-mint { 77 | color: $palette-mint; 78 | } 79 | 80 | .palette-bg-dark-mint { 81 | background-color: $palette-dark-mint; 82 | } 83 | 84 | .palette-dark-mint { 85 | color: $palette-dark-mint; 86 | } 87 | 88 | .palette-bg-medium-petrol { 89 | background-color: $palette-medium-petrol; 90 | } 91 | 92 | .palette-medium-petrol { 93 | color: $palette-medium-petrol; 94 | } 95 | 96 | .palette-bg-petrol { 97 | background-color: $palette-petrol; 98 | } 99 | 100 | .palette-petrol { 101 | color: $palette-petrol; 102 | } 103 | 104 | .palette-bg-salmon { 105 | background-color: $palette-salmon; 106 | } 107 | 108 | .palette-salmon { 109 | color: $palette-salmon; 110 | } 111 | 112 | .palette-bg-rose { 113 | background-color: $palette-rose; 114 | color: white; 115 | } 116 | 117 | .palette-rose { 118 | color: $palette-rose; 119 | } 120 | -------------------------------------------------------------------------------- /source/public-appearances.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: "default" 3 | name: "Talks and workshops by Lucas van Lierop" 4 | --- 5 | 6 |
7 | 8 |

9 | Public appearances 10 |

11 | 12 |

13 | Because of my work for DomCode I got interested in public speaking 14 | myself. Below you can see some of my recent talks, workshops etc. I mostly speak about software but occasionally 15 | other topics like welding (which is one of my favorite things to do) pop up too. I'm currently planning new talks/workshops for 2017. 16 |

17 | 18 | 81 |
82 | -------------------------------------------------------------------------------- /source/work.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: "default" 3 | name: "Lucas van Lierop's work" 4 | --- 5 | 6 |
7 | 8 |

9 | My Work 10 |

11 |

12 | This is some of the work I did at my former employers. 13 |

14 | 15 | 63 |
64 | -------------------------------------------------------------------------------- /src/SculpinCodeBlockBundle/ConvertListener.php: -------------------------------------------------------------------------------- 1 | tags to Twig {% codeblock %} tags so they can be 15 | * highlighted by the TwigCodeBlockBundle 16 | */ 17 | class ConvertListener implements EventSubscriberInterface 18 | { 19 | /** 20 | * {@inheritdoc} 21 | */ 22 | public static function getSubscribedEvents() 23 | { 24 | return array( 25 | Sculpin::EVENT_AFTER_CONVERT => 'afterConvert', 26 | ); 27 | } 28 | 29 | public function afterConvert(ConvertEvent $convertEvent) 30 | { 31 | if ($this->isMarkdownEvent($convertEvent) === false) { 32 | return; 33 | } 34 | 35 | $convertEvent 36 | ->source() 37 | ->setContent( 38 | $this->convertHtmlCodeElementsToTwigCodeBlocks( 39 | $convertEvent->source()->content() 40 | ) 41 | ); 42 | } 43 | 44 | /** 45 | * 46 | */ 47 | private function convertHtmlCodeElementsToTwigCodeBlocks(string $html): string 48 | { 49 | $document = new DOMDocument(5, 'utf-8'); 50 | 51 | if (@$document->loadHTML('' . $html) === false) { 52 | echo sprintf( 53 | "Could not parse html '%s', error was '%s'", 54 | $html, 55 | error_get_last() 56 | ); 57 | return $html; 58 | } 59 | 60 | $xpath = new DOMXPath($document); 61 | /** 62 | * Note that replacing elements in document does not work in a foreach loop 63 | */ 64 | while ($codeBlock = $xpath->query('//pre/code')[0]) { 65 | $this->replaceHtmlCodeBlockWithTwigCodeBlock($codeBlock, $document); 66 | } 67 | 68 | return preg_replace( 69 | [ 70 | '##', 71 | '##' 72 | ], 73 | '', 74 | $document->saveHTML($document->getElementsByTagName('body')[0]) 75 | ); 76 | } 77 | 78 | private function isMarkdownEvent(ConvertEvent $convertEvent): bool 79 | { 80 | return $convertEvent->isHandledBy( 81 | SculpinMarkdownBundle::CONVERTER_NAME, 82 | SculpinTwigBundle::FORMATTER_NAME 83 | ); 84 | } 85 | 86 | private function replaceHtmlCodeBlockWithTwigCodeBlock(DOMElement $codeBlock, DOMDocument $document): void 87 | { 88 | $language = $codeBlock->attributes->getNamedItem('class')->nodeValue; 89 | 90 | // Cannot be empty 91 | if ($language === null) { 92 | $language = ''; 93 | } 94 | 95 | $preNode = $codeBlock->parentNode; 96 | 97 | $newNode = $document->createCDATASection( 98 | <<<"CODE" 99 | {% codeblock lang:$language %} 100 | $codeBlock->nodeValue 101 | {% endcodeblock %} 102 | CODE 103 | ); 104 | 105 | $preNode->parentNode->replaceChild( 106 | $newNode, 107 | $preNode 108 | ); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /source/_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{ page.name }} {{ page.title }} 9 | 10 | {% block head_meta %} 11 | 12 | {% endblock %} 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | Lucas van Lieropfreelance software developer 23 | 31 |
32 | 33 |
34 | {% block content_wrapper %} 35 | {% block content %} 36 | {{ page.content }} 37 | {% endblock %} 38 | {% endblock %} 39 | 40 |
41 | 42 | 69 | 70 | {% if site.env == 'prod' %} 71 | 82 | {% endif %} 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /source/_views/post.html: -------------------------------------------------------------------------------- 1 | {% extends "default" %} 2 | 3 | {% block head_meta %} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {% endblock %} 17 | 18 | {% block content_wrapper %} 19 |
20 |
21 |

{{ page.title }}

22 |
23 |
24 | {{ page.imageAlt }} 25 |
26 | {{ post.date | date("Y, F jS") }} 27 |
28 | {{ page.blocks.content|raw }} 29 |
30 | {% if page.categories %} 31 |

32 | Categories: 33 | {% for category in page.categories %} 34 | {{ category }}{% if not loop.last %}, {% endif %} 35 | {% endfor %} 36 |

37 | {% endif %} 38 | {% if page.tags %} 39 |

40 | Tags: 41 | {% for tag in page.tags %} 42 | {{ tag }}{% if not loop.last %}, {% endif %} 43 | {% endfor %} 44 |

45 | {% endif %} 46 | 47 | {% if page.previous_post or page.next_post %} 48 | 58 | {% endif %} 59 |
60 | 61 | {% if site.disqus.shortname and site.disqus.shortname != '' %} 62 |
63 | 83 | 86 | {% endif %} 87 | 88 | {% endblock %} 89 | -------------------------------------------------------------------------------- /source/projects/electric-discovery.md: -------------------------------------------------------------------------------- 1 | --- 2 | draft: true 3 | layout: "default" 4 | name: "Electric Land Rover Discovery project" 5 | 6 | --- 7 | 8 | [Component Overview](/projects/electric-discovery/component-overview) 9 | 10 | 11 | 12 | 13 | 14 | 15 | I'm working on converting my a Land Rover Discovery from diesel to electric. 16 | 17 | It's a 1997 300Tdi model. 18 | This model originally had a 100 inch wheelbase but it has been [stretched to ~124 inch](). 19 | Both body and chassis have been [hot dip galvanized]() 20 | 21 | One of my examples is the [electric Volvo Amazon](https://www.oudevolvo.nl/ev-combi/) project by [Lars Rengersen](https://twitter.com/larsrengersen) 22 | 23 | I'll be using the same (Siemens 1PV5153 4WS-14) 3 phase induction motor as Lars [put it in his Volvo](https://www.oudevolvo.nl/blog/2015/08/13/m400-versnellingsbak-aan-elektromotor-gemaakt-en-ingebouwd/) 24 | with a different controller: the [Sevcon gen 4 size 8](http://www.sevcon.com/products/high-voltage-controllers/gen4-s8/) 25 | 26 | The Siemens motor was originally developed for the [electric Ford Transit Connect](https://en.wikipedia.org/wiki/Azure_Transit_Connect_Electric) 27 | but came available to the DIY market after the bankruptcy of [Azure Dynamics](https://en.wikipedia.org/wiki/Azure_Dynamics). 28 | 29 | In the Ford Transit Connect the Siemens motor was used in combination with the [DMOC 645](http://store.evtv.me/proddetail.php?prod=dmoc645) controller however 30 | it becomes harder to obtain both the motor and the controller from the Azure Dynamics bankruptcy sales. 31 | 32 | Also to get it road legal in the Netherlands you need a certificate which proves the [electromagnetic compatibility](https://en.wikipedia.org/wiki/Electromagnetic_compatibility) of the motor controller combination for the Dutch market. 33 | This reduces both the number of motors/controllers you can use as well as the suplliers you can get them from. 34 | 35 | Luckily [New Electric](http://www.newelectric.nl/drive-train/) has already done the necessary EMC tests and they can supply a motor/controller combination with the required certificated. 36 | Furthermore they aquire the motors directly from Siemens rather 37 | 38 | The rest of the driveline stay the same and I'll be [keeping the gearbox]() albeit with a few small changes. 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | Other people using Tesla Model S batteries: 81 | - http://evbimmer325i.blogspot.nl/2017/06/tesla-battery-pack-bus-bar-steel.html?m=1 82 | - http://www.diyelectriccar.com/forums/showthread.php?t=177489 83 | - https://www.oudevolvo.nl/ev-combi/ 84 | -------------------------------------------------------------------------------- /source/_posts/draft-dealing-with-unavailable-services-on-application-start-up.md: -------------------------------------------------------------------------------- 1 | --- 2 | draft: true 3 | title: Dealing with unavailable services on application start up 4 | categories: 5 | software-development 6 | tags: 7 | - docker 8 | - docker-festival 9 | --- 10 | 11 | Containers are volatile, I've written before on how that creates issues when 12 | [proxying ingress traffic](/blog/2017/06/25/accessing-your-docker-app-via-a-domain-name-using-traefik/) 13 | 14 | Another area where this is creating problems is when processes have (a chain of) dependencies on each other. 15 | 16 | Many containerized applications are composed of multiple processes like for example a web server a run time a relational database, a key/value store. 17 | These processes will most likely have (a chain of) dependencies other services. 18 | 19 | TODO: health check/parallelism 20 | 21 | 22 | # Example 1, check if a web application is ready to serve requests 23 | 24 | A database is a good example since it touches multiple issues like connections, state. 25 | 26 | A simple dependency chain might look like this 27 | 28 | In development: 29 | - In order to start the application it's database should be migrated to the latest state 30 | - In order to execute database migrations vendor libraries must be installed 31 | - In order to execute database migrations the database needs to be available 32 | - In order to be available the database must be started. 33 | 34 | In production: 35 | - In order to add a newly deployed application to the pool it must be [healthy] 36 | - In order for an application to be healthy it must be started. 37 | - In order to start the application it's database should be migrated to the latest state 38 | - In order to execute database migrations the database needs to be available 39 | 40 | 41 | TODO: forward compatible migrations/config 42 | 43 | /*** 44 | 45 | Note that if your happen to be using Doctrine ORM there's a caveat: 46 | You need to installl vendor libraries with composer to execute DB polling 47 | However composer install also triggers a clear cache script which in turn tries to generate proxies 48 | for each database entity. This of course fails horribly when there's no database connection yet. 49 | An easy solution is to have Doctrine generating proxies on demand. 50 | 51 | In Symfony this can be configured as follows: 52 | 53 | ```yaml 54 | doctrine: 55 | orm: 56 | # This essentially allows CLI scripts to run when there is no database available (yet) 57 | # If disabled Doctrine will attempt to generate the proxy cache on cache warmup. 58 | auto_generate_proxy_classes: true 59 | ``` 60 | 61 | ***/ 62 | 63 | 64 | 65 | Since all of these any of these can fail how do you make sure your application handles these correctly? 66 | 67 | Let's solve the last problem first: 68 | 69 | # Example 2: start a database when an application starts 70 | This is mainly about development/test environments where the database is also a containerized process whereas 71 | in a production the database is most likely running as an external service. 72 | 73 | I often use the [official Docker MySQL image](https://hub.docker.com/_/mysql/) for development and testing. 74 | The easiest way to make sure the database is started when you run an application (or test suite!) 75 | depending is to use docker compose `depends_on` option. 76 | To trigger starting the database when the app starts configure something like: 77 | feature: 78 | 79 | ```yaml 80 | version: "2" 81 | 82 | services: 83 | app: 84 | ... 85 | depends_on: 86 | - db 87 | db: 88 | ... 89 | ``` 90 | 91 | # Example 2: Waiting until the database is ready 92 | 93 | Starting a database might take some time, in most cases it takes longer than starting the application depending on it. 94 | So how to determine when the database is ready? 95 | While I'm a fan of event driven systems. The easiest way to determine this is plain old polling. 96 | A.k.a: just attempt to connect every second or so. 97 | This is preferably done using via the application database abstraction layer. 98 | This way one can be sure that not only the database is ready but also the application is correctly configured 99 | to make a connection. 100 | 101 | Since most of my applications are written in PHP and based on the Symfony framework 102 | I often use the [LIIP Monitor Bundle](https://github.com/liip/LiipMonitorBundle). 103 | This library is able to check various kinds of pre conditions. 104 | Testing database connections is just one of the many things it can do. 105 | There is probably a similar solution for your language/framework of choice. 106 | 107 | `wait-for-services` 108 | 109 | ```bash 110 | #!/usr/bin/env sh 111 | 112 | DIR=`dirname $(readlink -f $0)` 113 | 114 | until ${DIR}/console monitor:health --all; do 115 | >&2 echo "Waiting for all services to become available" 116 | sleep 1 117 | done 118 | 119 | ``` 120 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL=/bin/bash 2 | .DELETE_ON_ERROR: 3 | 4 | TARGET=$@ 5 | 6 | export HOST_UID=$(shell id -u) 7 | export HOST_GID=$(shell id -g) 8 | 9 | PLATFORM := $(shell uname -s) 10 | ifeq ($(PLATFORM),Darwin) 11 | export DOCKER_HOST_IP_OR_NAME = docker.for.mac.localhost 12 | else ifeq ($(PLATFORM),Linux) 13 | export DOCKER_HOST_IP_OR_NAME=$(shell ip -f inet addr show docker0 | grep -Po 'inet \K[\d.]+') 14 | endif 15 | 16 | 17 | include make/travis.mk 18 | 19 | all: docker/app/.built 20 | 21 | export COMPOSE_PROJECT_NAME=lucasvanlierop-website 22 | export CI_FILE='env/ci/docker-compose.yml' 23 | 24 | ~/.composer: 25 | $(TARGET_MARKER_START) 26 | mkdir -p ~/.composer 27 | $(TARGET_MARKER_END) 28 | 29 | vendor: \ 30 | docker/sculpin/.built \ 31 | ~/.composer \ 32 | composer.json \ 33 | composer.lock 34 | $(TARGET_MARKER_START) 35 | docker-compose -f $(CI_FILE) run --rm sculpin composer install 36 | $(TARGET_MARKER_END) 37 | 38 | .PHONY: 39 | clean: 40 | $(TARGET_MARKER_START) 41 | rm -rfv source/css/* 42 | rm -rfv output_prod/* 43 | $(TARGET_MARKER_END) 44 | 45 | source/css/pygments.css: docker/sculpin/.built 46 | $(TARGET_MARKER_START) 47 | docker-compose -f $(CI_FILE) run --rm sculpin sh \ 48 | bin/generate-pygments-css 49 | $(TARGET_MARKER_END) 50 | 51 | docker/sass/.built: \ 52 | $(shell find docker/sass/* | grep .built) 53 | $(TARGET_MARKER_START) 54 | docker-compose -f $(CI_FILE) build sass 55 | touch $@ 56 | $(TARGET_MARKER_END) 57 | 58 | docker/sculpin/.built: docker/sculpin/* 59 | $(TARGET_MARKER_START) 60 | docker-compose -f $(CI_FILE) build sculpin 61 | touch $@ 62 | $(TARGET_MARKER_END) 63 | 64 | output_dev: 65 | $(TARGET_MARKER_START) 66 | mkdir -p $(TARGET) 67 | $(TARGET_MARKER_END) 68 | 69 | # todo fix source/* 70 | output_prod: \ 71 | $(shell find source/) \ 72 | source/css \ 73 | docker/sculpin/.built \ 74 | vendor 75 | $(TARGET_MARKER_START) 76 | docker-compose -f $(CI_FILE) run --rm sculpin vendor/bin/sculpin generate \ 77 | --env=prod 78 | $(TARGET_MARKER_END) 79 | 80 | source/css: \ 81 | docker/sass/.built \ 82 | source/css/pygments.css \ 83 | $(shell find source/scss/ | grep .built) 84 | $(TARGET_MARKER_START) 85 | docker-compose -f $(CI_FILE) run --rm sass --update /app/source/scss:/app/source/css 86 | touch $@ 87 | $(TARGET_MARKER_END) 88 | 89 | docker/app/.built: \ 90 | docker/app/* \ 91 | output_prod 92 | $(TARGET_MARKER_START) 93 | docker-compose -f $(CI_FILE) build app 94 | touch $@ 95 | $(TARGET_MARKER_END) 96 | 97 | .PHONY: up 98 | up: output_dev 99 | docker-compose up 100 | 101 | .PHONY: down 102 | down: 103 | docker-compose down 104 | 105 | .PHONY: test 106 | test: docker/app/.built 107 | $(TARGET_MARKER_START) 108 | docker-compose -f $(CI_FILE) up -d --no-build --force-recreate --remove-orphans 109 | tests/smoke-test.sh 110 | tests/validate-html.sh 111 | docker-compose -f $(CI_FILE) stop 112 | $(TARGET_MARKER_END) 113 | 114 | DOCKER_TUNNEL_CONTAINER=docker_swarm_ssh_tunnel 115 | DOCKER_TUNNEL_PORT=12374 116 | DOCKER_SWARM_HOST=lucasvanlierop.nl 117 | DEPLOY_USER=deploy 118 | DOCKER_STACK_FILE=env/prod/docker-compose.yml 119 | DOCKER_STACK_NAME=lucasvanlierop-website 120 | 121 | .PHONY: deploy 122 | .SILENT: deploy 123 | deploy: \ 124 | traefik-production-certificate-store \ 125 | traefik-production-config \ 126 | tunnel-to-production-docker-socket 127 | $(TARGET_MARKER_START) 128 | docker \ 129 | -H localhost:$(DOCKER_TUNNEL_PORT) \ 130 | stack deploy \ 131 | --with-registry-auth \ 132 | -c $(DOCKER_STACK_FILE) \ 133 | --prune \ 134 | $(DOCKER_STACK_NAME) 135 | $(TARGET_MARKER_END) 136 | 137 | .PHONY: traefik-production-certificate-store 138 | .SILENT: traefik-production-certificate-store 139 | traefik-production-certificate-store: 140 | $(TARGET_MARKER_START) 141 | # Create file Where Traefik can store it's certificates 142 | ssh $(DEPLOY_USER)@$(DOCKER_SWARM_HOST) "(umask 600; touch /opt/traefik/acme.json)" 143 | $(TARGET_MARKER_END) 144 | 145 | .PHONY: traefik-production-config 146 | .SILENT: traefik-production-config 147 | traefik-production-config: 148 | $(TARGET_MARKER_START) 149 | # Copy Traefik config file (Todo: this should be Swarm secret since this does not trigger a restart) 150 | scp env/prod/traefik.toml $(DEPLOY_USER)@$(DOCKER_SWARM_HOST):/opt/traefik/traefik.toml 151 | $(TARGET_MARKER_END) 152 | 153 | .SILENT: tunnel-to-production-docker-socket 154 | tunnel-to-production-docker-socket: 155 | $(TARGET_MARKER_START) 156 | # Create SSH tunnel to Docker Swarm cluster 157 | @(docker ps | grep $(DOCKER_TUNNEL_CONTAINER)) || docker run \ 158 | -d \ 159 | --name $(DOCKER_TUNNEL_CONTAINER) \ 160 | -p $(DOCKER_TUNNEL_PORT):$(DOCKER_TUNNEL_PORT) \ 161 | -v $(SSH_AUTH_SOCK):/ssh-agent \ 162 | kingsquare/tunnel \ 163 | *:$(DOCKER_TUNNEL_PORT):/var/run/docker.sock \ 164 | $(DEPLOY_USER)@$(DOCKER_SWARM_HOST) 165 | 166 | timeout 10 $(MAKE) wait-for-tunnel-to-production-docker-socket 167 | 168 | $(TARGET_MARKER_END) 169 | 170 | wait-for-tunnel-to-production-docker-socket: 171 | until docker -H localhost:$(DOCKER_TUNNEL_PORT) version 2>/dev/null 1>/dev/null > /dev/null; do \ 172 | echo "Waiting for docker tunnel"; \ 173 | sleep 1; \ 174 | done 175 | -------------------------------------------------------------------------------- /source/_posts/draft-tools-from-the-golden-years-an-introduction-to-make.md: -------------------------------------------------------------------------------- 1 | --- 2 | draft: true 3 | title: Tools from the Golden Years, an introduction to (GNU) Make 4 | categories: 5 | software-development 6 | tags: 7 | - make 8 | image: /images/blog/software/empowered-by-gnu.svg 9 | --- 10 | 11 | One of the tools I use almost every day is (GNU) Make. 12 | 13 | Since I will use Make in some of my following posts I'd thought I should give a very basic introduction. 14 | This post is also meant to share my love for it and hopefully give you an incentive to try it. 15 | 16 | If you find yourself creating cache directories, installing vendor libraries, compiling assets/artefacts, running tests, deploying using different scripts keep reading! 17 | 18 | What I wanted was having to worry about only a very limited set of commands for all of the above. 19 | Most of them aren't things you need to execute directly but mostly a requirement of some other command. 20 | Make is very good in glueing all kinds of scripts and their requirements together. 21 | 22 | 23 | ## But Make is very old, how can it still be useful? 24 | 25 | Yes Make is very old. I was really surprised when I found out how old it really was, 26 | I knew it was from #backinthedays but [according to wikipedia](https://en.wikipedia.org/wiki/Make_(software)) 27 | it first occured in April 1976! 28 | At the moment of writing that's exactly __42(!)__ years ago (and still going strong). 29 | 30 | The seventies really were [the Golden Years](https://www.youtube.com/watch?v=JUuRGRcY9O0) of Unix 31 | 32 | a few years after [the dawn of time](https://en.wikipedia.org/wiki/Unix_time))! 33 | 34 | ## What does Make even do? 35 | Make was (and still is) meant to compile C source files. 36 | However it can be used for many other applications too. 37 | That said it's important to keep in mind it's original purpose. 38 | 39 | What make does is running shell commands. 40 | In case you wonder why would I need a tool to run shell commands if I already have a shell? 41 | 42 | Well what makes make powerful is how it can orchestrate a (large) amount of separate calls in a specific order. 43 | 44 | Especially with containers Make is a great fit since it's very good at abstracting the boiler plate of running containers. 45 | 46 | # Use cases 47 | 48 | In my Job was web application developer I have many use cases for Make, think of: 49 | 50 | - Building Docker images 51 | - Ensuring cache directories exist 52 | - Installing vendor libraries (using PHP's Composer, Node's yarn) 53 | - Running test & inspection tools (PHPunit etc.) 54 | - Booting the application in development modus 55 | - Creating dist(ribution) versions of the application 56 | - Deploying the application 57 | 58 | Even while most of these actions run in containers there are always difference between 59 | host operating systems, mainly Linux and OSX in my case. 60 | Make is capable of adapting to various environments in a simple but effective way: setting variables containing paths, 61 | command flags etc. 62 | 63 | ## The anatomy of a Make target 64 | 65 | A make goal consists of a target, optional pre-requisites and a rule 66 | 67 | Where a target is a file that should be created. 68 | The pre-requisites are other files that should be created first in order to create the target 69 | The rule is a 70 | 71 | The are declared as follows: 72 | 73 | ```makefile 74 | a-target: a-pre-requisite 75 | a-rule 76 | ``` 77 | 78 | Alternatively it's also possible to run commands that do not result in a target. 79 | These are called [Phony targets](https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html). 80 | Phony targets can be used to run tests, clean actions etc. 81 | 82 | ```makefile 83 | .PHONY: logs 84 | logs: 85 | tail -f /var/log/some.log 86 | ``` 87 | 88 | ## How I started using Make the wrong way 89 | I intially started using Make as an alternative for Apache Ant 90 | which one of my previous employers used for running tests and generating deployable artefacts. 91 | When I got into using (Docker) containers I Discovered GNU Make via [Jessie Frazelle](https://twitter.com/jessfraz) 92 | 93 | While it's syntax is a bit quirky Make made so much more sense to me than the bulky XML files I was used to. 94 | I started out with (ab)using Make as a dumb task runner like: (`PHONY` galore) 95 | 96 | ```makefile 97 | 98 | .PHONY: do-something 99 | do-something: 100 | shell-command 101 | ``` 102 | 103 | Using `.PHONY` is almost like a code smell to me now. 104 | In many cases it's better to use real target or [empty targets](https://www.gnu.org/software/make/manual/html_node/Empty-Targets.html) 105 | 106 | ...overlooked 107 | 108 | ## The power of dependency resolving. 109 | Dependency (or pre-requisites as Make likes to call them) resolving might be the single most powerful feature of make. 110 | ...Make will create a graph of 111 | 112 | ## Debugging 113 | ...--debug, silent 114 | 115 | 116 | ## Example 117 | 118 | A very common scenrar 119 | 120 | 121 | ----------------------------- Next post 122 | 123 | ## Advanced, running things in parallel 124 | Depending on the resources available Make can execute it's targets more efficiently by running them in parallel (using the `-j` argument) 125 | 126 | ```bash 127 | make -j --output-sync=recurse target-1 target-2 (... target-n) 128 | ``` 129 | 130 | The `--output-sync=recurse` buffers the output created by a given target until it has finished. 131 | In most cases this is what you want because mixed output from multiple targets is pretty unreadble. 132 | 133 | 134 | 135 | Make isn't really good in Idempotency 136 | 137 | --- 138 | Tnx Jessie, Jan 139 | -------------------------------------------------------------------------------- /source/scss/main.scss: -------------------------------------------------------------------------------- 1 | @import "modules/_colors"; 2 | 3 | html { 4 | height: 100%; 5 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; 6 | } 7 | 8 | html, body { 9 | min-height: 100% !important; 10 | width: 100% !important; 11 | margin: 0; 12 | padding: 0; 13 | color: #2C3E50; 14 | } 15 | 16 | a { 17 | color: $palette-petrol; 18 | text-decoration: none; 19 | } 20 | 21 | body { 22 | line-height: 1.4; 23 | background-color: white; 24 | } 25 | 26 | header.top { 27 | color: #eee; 28 | padding: 20px; 29 | 30 | a { 31 | text-decoration: none; 32 | color: white; 33 | float: left; 34 | } 35 | } 36 | 37 | span.top-title h1, h2, h3 { 38 | letter-spacing: .6px; 39 | font-weight: 300; 40 | } 41 | 42 | span.top-title { 43 | margin: 0; 44 | font-size: 200%; 45 | } 46 | 47 | span.top-title .payoff { 48 | font-size: 50%; 49 | display: block; 50 | } 51 | 52 | header.top { 53 | nav { 54 | margin-top: 12px; 55 | float: right; 56 | ul { 57 | margin: 0; 58 | padding: 0; 59 | display: inline; 60 | 61 | li { 62 | margin: 0; 63 | padding: 0; 64 | float: left; 65 | list-style: none; 66 | padding: 0 10px; 67 | text-transform: uppercase; 68 | border-right: 1px solid white; 69 | 70 | &:last-child { 71 | border: none; 72 | } 73 | } 74 | } 75 | 76 | a { 77 | color: white; 78 | } 79 | } 80 | } 81 | 82 | main, header.top, footer { 83 | clear: both; 84 | border-bottom: solid 1px white; 85 | padding: 10px 20px; 86 | } 87 | 88 | header.top:after { 89 | visibility: hidden; 90 | display: block; 91 | font-size: 0; 92 | content: " "; 93 | clear: both; 94 | height: 0; 95 | } 96 | 97 | section h2 { 98 | text-transform: uppercase; 99 | } 100 | 101 | .about-me h2 { 102 | display: none; 103 | } 104 | 105 | a.button { 106 | font-size: 200%; 107 | font-weight: bold; 108 | padding: 5px 10px; 109 | border-radius: 4px; 110 | color: white; 111 | } 112 | 113 | .block { 114 | list-style: none; 115 | padding: 20px; 116 | margin-top: 40px; 117 | background: rgba(50, 50, 50, .05); 118 | border: solid 1px lightgrey; 119 | border-radius: 3px; 120 | 121 | h3 { 122 | margin-top: 0; 123 | 124 | .fa { 125 | font-size: 200%; 126 | margin-right: 10px; 127 | } 128 | .fa-plus-circle { 129 | font-size: 100%; 130 | margin: 0; 131 | } 132 | } 133 | } 134 | 135 | .block-list { 136 | margin: 0; 137 | padding: 0; 138 | } 139 | 140 | .main { 141 | background-color: #eee; 142 | padding: 20px; 143 | height: 100%; 144 | margin: 0; 145 | } 146 | 147 | .photo { 148 | float: right; 149 | height: auto; 150 | max-width: 100%; 151 | margin-left: 1em; 152 | margin-bottom: 1em; 153 | } 154 | 155 | .contact-links { 156 | margin: 0; 157 | padding: 0; 158 | 159 | li { 160 | list-style: none; 161 | margin: 10px 0 0 0; 162 | 163 | i { 164 | font-size: 20px; 165 | } 166 | } 167 | } 168 | 169 | footer { 170 | background-color: #ddd; 171 | } 172 | 173 | abbr[title] { 174 | cursor: help; 175 | border-bottom: 1px dashed $palette-grey; 176 | text-decoration: none; 177 | } 178 | 179 | @media screen and (min-width: 640px) { 180 | main, header.top, footer { 181 | padding: 20px 20%; 182 | } 183 | } 184 | 185 | @media screen and (max-width: 640px) { 186 | body { 187 | font-size: 1.3em; 188 | } 189 | .block { 190 | margin-top: 20px; 191 | } 192 | span.top-title .payoff { 193 | display: none; 194 | } 195 | header.top { 196 | a { 197 | text-align: center; 198 | float: none; 199 | } 200 | nav { 201 | float: none; 202 | 203 | ul { 204 | display: block; 205 | margin: 0 auto; 206 | 207 | li { 208 | float: none; 209 | display: inline-block; 210 | } 211 | } 212 | } 213 | } 214 | abbr[title] { 215 | border: none; 216 | } 217 | abbr[title]:before { 218 | content: attr(title) " (" 219 | } 220 | abbr[title]:after { 221 | content: ")" 222 | } 223 | } 224 | 225 | figure.code-highlight-figure { 226 | margin: 0; 227 | overflow-x: scroll; 228 | 229 | .highlight { 230 | padding: 10px; 231 | } 232 | } 233 | 234 | ul.categories { 235 | margin: 0; 236 | padding: 0; 237 | display: inline; 238 | 239 | li { 240 | margin: 0; 241 | padding: 0; 242 | display: inline; 243 | list-style: none; 244 | padding: 0 10px 0 0; 245 | text-transform: uppercase; 246 | 247 | &:last-child { 248 | border: none; 249 | } 250 | } 251 | } 252 | 253 | article { 254 | img { 255 | max-width: 100%; 256 | } 257 | } 258 | 259 | .blog { 260 | h3 { 261 | clear: both; 262 | 263 | .image { 264 | display: block; 265 | height: 100px; 266 | width: 100px; 267 | background-repeat: no-repeat; 268 | background-position: center; 269 | background-size: cover; 270 | float: left; 271 | margin-right: 20px; 272 | } 273 | } 274 | } 275 | 276 | 277 | code { 278 | background-color: #f6f8fa; 279 | border: solid 1px darkgray; 280 | } 281 | -------------------------------------------------------------------------------- /source/_posts/draft-testing-containerized-web-applications-with-docker-part-1-smoke-tests.md: -------------------------------------------------------------------------------- 1 | --- 2 | draft: true 3 | title: Testing containerized web applications with Docker part 1: Smoke tests 4 | categories: 5 | - software-development 6 | tags: 7 | - testing 8 | - docker 9 | image: /images/blog/software/smoke-and-ashes.jpg 10 | imageAlt: Smoke and ashes 11 | --- 12 | 13 | _This article is part of a [series of blog posts](/blog/tags/docker-festival/) related to the 14 | [Docker Festival](https://twitter.com/hashtag/dockerfestival?src=hash) style of workshops 15 | [Matthias Noback](https://twitter.com/matthiasnoback) are doing._ 16 | 17 | One of the great promises of containerized infrastructure is being able to run same stack (Operating system + runtime + application etc) in multiple environments. 18 | However it seems not everyone uses this to it's full potential. 19 | 20 | - eyeopener, run app, not just test code but exact stack that will be deplo artefact 21 | verify what will be deployed 22 | 23 | 24 | While it's fantastic to have (almost) no differences between development and production it's even better to have no differences between development, __CI__ and production. 25 | Before the container era many CI environments were provisioned in a different way than development and production where tools like Ansible, Puppet etc. were used. 26 | 27 | By far the biggest issue with tools like these is that are meant to update mutable infrastructure. 28 | After a while both local (e.g. Vagrant based) development environments as well as production (like) environments are more or less up to date. 29 | This means that provisioning almost never runs from scratch and breaking changes might be introduced without being noticed. 30 | Until a new server is added or a new employee joins the team that is. 31 | 32 | I've seen many projects we're 33 | Because building a container image and starting it is a lot simpler than running a provisioning tool against a CI environment 34 | it's now possible to not only run unit/integration tests but also real system tests. 35 | 36 | This article will be an introduction to system testing a containerized application in a test/CI environment. 37 | 38 | ## What is smoke testing? 39 | Smoke testing is the most basic form of [system testing](https://en.wikipedia.org/wiki/System_testing). 40 | Essentially it should be very simple tests that still cover a lot of the application and the stack it runs on. 41 | When a smoke test fails there's something broken which should be fixed before doing further (system) testing. 42 | 43 | What about the smoke?, well Wikipedia [explains system testing software](https://en.wikipedia.org/wiki/Smoke_testing) 44 | as "trying the major functions of software before carrying out formal testing". 45 | The explanation for system testing electrical items is informative though: 46 | "looking for smoke when powering electrical items for the first time" 47 | 48 | A small typo or misconfiguration can cause an entire (web) application to fail (even when all unit tests etc. have passed). 49 | Many of these errors can be caught by testing something trivial as "can the homepage of the web application be accessed?" 50 | 51 | Since it's easy (and fast) to start containerized (web) applications in a test environment smoke testing might be more powerful than ever. 52 | 53 | ## Configuring the application to run in a CI 54 | 55 | ## Example #1, test if an application responds at all. 56 | 57 | Since applications are started 'just in time' for testing 58 | I've wrote earlier on how that raises challenges when trying to 59 | [proxy ingress traffic to web services](/blog/2017/06/25/accessing-your-docker-app-via-a-domain-name-using-traefik/). 60 | 61 | Instead of dealing with specific container instances it's better to handle the processes as named services and use them that way 62 | 63 | Another area where this raises challenges is when directly accessing the service (not a specific container), for example during testing. 64 | When running system tests against a web service, chances are the service hasn't finished starting yet or maybe it's waiting on another service. 65 | 66 | Especially in a CI environment services are started just in time and most likely in the background. 67 | So how to determine if web service is ready for use? 68 | 69 | ## Polling 70 | 71 | The simple answer is: poll it. 72 | While event based systems are very nice, the easiest way by far to check if a web service is ready to handle requests is do a request. 73 | 74 | A very simple tool which is available almost everywhere is: `curl`. 75 | If it's called with the `--fail` argument it will fail (almost) silently on all server errors. 76 | *(To make it completely silent also add the `--silent` argument)* 77 | 78 | So as longs as `curl` requests keep failing the web service is not ready yet. 79 | 80 | The following example tests a given url every 0.5 seconds: 81 | 82 | ```bash 83 | #!/bin/sh 84 | 85 | set -e 86 | 87 | until test_output=`curl --silent --fail ${1}`; do 88 | echo "Waiting for the web service to become available ${1}" 89 | sleep 0.5; 90 | done 91 | ``` 92 | 93 | ## Make sure polling doesn't continue forever. 94 | 95 | Of course it's always possible that something is broken and the service will never return a success status code. 96 | To prevent the script from running forever it's advised let it time out after a while. 97 | 98 | This can be done using the shell `timeout` function which can be prefixed to a command. 99 | Assuming the poll script above can be found at `poll-web-service` an example of 'poll until timeout` looks like this: 100 | 101 | ```bash 102 | timeout -t 60 poll-web-service {url-to-test} 103 | ``` 104 | 105 | To prevent issues with different shell versions on different systems it's safer to run this in a container too. 106 | The following example assumes there's a Docker Compose based setup with a (development) 107 | service named `app` and service named `web` which are in the same network. 108 | 109 | ```bash 110 | docker-compose run --rm app sh -c 'timeout -t 60 poll-web-service http://web' 111 | ``` 112 | 113 | In a next article more advanced system tests will be covered 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /source/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: "default" 3 | name: "Lucas van Lierop, freelance software developer #php #symfony #docker" 4 | 5 | generator: pagination 6 | pagination: 7 | max_per_page: 3 8 | use: 9 | - posts 10 | --- 11 | 12 |
13 | 14 | 15 |

About me

16 | 17 | this is me 18 | 19 |

20 | I'm a freelance software developer based in Utrecht, the Netherlands. I'm specialized in creating, refactoring and containerizing 21 | (web) applications. 22 |

23 | 24 | 45 |
46 | 47 |
48 | 49 |

50 | What I can do for you 51 |

52 | 128 |
129 | 130 | 131 | 161 | -------------------------------------------------------------------------------- /source/_posts/draft-history-of-the-electric-discovery.md: -------------------------------------------------------------------------------- 1 | --- 2 | draft: true 3 | title: Starting the EV conversion project for the Land Rover 4 | tags: 5 | - land rover 6 | - ev conversion 7 | - project-discover-e 8 | categories: 9 | - cars 10 | --- 11 | 12 | # Vlog 13 | 14 | This is the story of My Land Rover discovert and how I converted it. I'm Lucas, professional software engineer by and I hobby mechanic. In this vlog I'll tell about my adventures in building and driving it. By now I feel bad that I didn't start vlogging about this project years ago like one of my examples Jehu Garcia, but at least I still have the pictures. Hopefully the project raises some questions so I can do a Q&A video. For now I will vlog the entire 9 years I've owned the car in one pilot episode 15 | 16 | 2008 17 | * Why I bought this vehicle? 18 | Well, I was rebuilding my house for over 2 years {insert flash: reconstuction pictures}. My parents – who always help me! - helped me out getting all kinds of building materials { flash picutres } and also helped me transporting all kinds of trash to the recyling station {insert pictures} 19 | 20 | However, sometimes I needed something when my parents weren't there and so I had to go by bike. I can tell you that's really hard {flash pictures} , sometimes I had to carry loads of more than 100 kg (picture}. At at given moment my boss bought a brand new Land Rover Discovery (flash picture + options ALLTHETHINGS + 80K+}. Then I made a decision I thougt: “this is enough, I'm not doing this anymore, I need to get myself a ride too!” So I also bought a Land Rover Discovery. It was also a diesel, it was also green (the color at least), 4x4, heavy etc. The -only- difference was that that was over 10 years old, dented, rusty, (Picutre of first ride|. But hey, I suddenly had my frst car, and it was a still a pretty cool car to drive. 21 | 22 | So now I had the car I could do my own hauling {insert hauling pictures}, I made new friends I could go to meetings {insert prikkersdag, andere rittten}l could drive up to the North Cape {insert pictures}. I could even drive to the Sahara {insert pictures, more on that later} And off couse it was an old car – English {insert funny English pictures, so it needed lot's of maintanance. I noticed repairs at the garagage we're hugely expensive {insert pictures of bills } so I bough a lot of tools. {insert pictures of tools } 23 | 24 | 2009 25 | 26 | After a while I found out it needed more than just maintainence, new parts and fluids. It needed welding {insert rusty picture} My dad – who knows me very well – said something like “Son welding is hard, try do not to do everything yourself {do all the things }. Why not lot let a professional do it?” So I decided to take it to a professional. After some research I though I had found a -'professional'-. Boy was I wrong... I hadso much debate with these guys and still ended up having to pay 3000 euro's { insert WTF?, pictures of money } But at least it was fixed now right, right? No… it wasn't , when I had the Vehicle checked a few days later it (which you have to do every year in the Netherlands) it failed...on rusty spots which needed welding {insert picture }. So the garage fixed the rusted spot and made sure it was ok again 27 | 28 | That was the time I made my second decision, from now on I will DIY everything I can, including welding. So I did a welding course {insert picture of certificate}. And I started welding on the car, sometimes at my parents place (insert pictures}, sometimes on the street at home {insert picture}. The welding light attracted many people walking by, mostly friendly neighors asking what I was up to {insert picture NICE } but also a guy (apparently a welder himself) who came up to me and said: 'you know what we call this at my work?, We call this SHIT” {insert picture, yeah, thanks buddy…}. I chose to ignore him but folks, if you're watching this: please never do this, if someone is trying to learn something wether professionally or just as an amateur. Wether it's welding, programming (I'm a programmer), cooking or whatever. Help them out, give them advice, {insert picture} ask a question.{ insert picture } Or, if you really don't care/think it's shit STFU {insert picture}. 29 | 30 | At that time I started thinking about how awesome it would be to convert the Discovery to a (hybrid) electric system. My plan was to attach a big generator { insert picture } to the engine {insert picture}, add some batteries {insert picture} and then replace the entire heavy, maintainence requiring driveline { insert picture} with just 4 wheels with built in hub motors which then seemed to be the furure { insert pictures }. However I did lot's of research and had discussions { insert pictures } and soon learned the time wasn't yet right for this both because of the costs and the availability of parts. So this plan was skipped. {insert meme skipped } 31 | 32 | 2010 33 | 34 | Anyway as I continued welding the car I realized it should be done properly, and that's something you can only do when the car is dismantled. Why: because it's hard to reach certain spots for welding, because if you CAN weld on one side, you can't always reach the other side to fix the burnt pain causing it to rust again but most important because you set the thing on fire {insert been there done that picture } SO another decision (insert pricture } :rip this thing apart and start fixing it properly….And have it treated afterwards. {insert pictures of treating } 35 | 36 | Also, while people started laughing at me when I said the car really wasn't that big it actually wasn't. It was maybe little wider than average cars althoug not that much. It was certainly higher, but not vey long. Actually it had the same wheelbase as my parents Renault Megane. And if I were to place passenger seats in it not many cargo space would be left. At least not enough for traveeling or hauling. So the plan was to make it bigger somehow, maybe just stretch it?! {insert picture stetch meme} But first: on the road again. 37 | 38 | So another trip. This time we would leave our continent for the first time, we would be heading for Morocco {insert picture} – or even cooler we're going to Africa! {insert picture of Africa} 39 | So I in preparation I had bought new tires, Hi-Jack lift {inset pictures, a snorkel, extra jerry cans, double battery system, working light etc. we drove up to Spain, took the boat to Tanger and drove around. It was an AMAZING trip{insert picture and music Don't go loose it baby } 40 | 41 | We returned home in octobober and I had 42 | 43 | 2011 44 | 45 | 46 | 47 | Bad things: 48 | * bills 49 | * People saying: “you're doing it wrong” 50 | * Cooling fluid 51 | * Fresh painted Hood getting loose 52 | 53 | -------------------------------------------------------------------------------- /source/_posts/2017-06-25-accessing-your-docker-app-via-a-domain-name-using-traefik.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Accessing your Docker app via a domain name using Træfɪk" 3 | categories: 4 | - software-development 5 | tags: 6 | - docker 7 | - docker-festival 8 | - traefik 9 | image: images/blog/software/traefik-header-image.png 10 | imageAlt: "Træfɪk architecture, courtesy of traefik.io" 11 | --- 12 | 13 | _This article is part of a [series of blog posts](/blog/tags/docker-festival/) related to the 14 | [Docker Festival](https://twitter.com/hashtag/dockerfestival?src=hash) workshop 15 | [Matthias Noback](https://twitter.com/matthiasnoback) and I did at 16 | [Dutch PHP Conference 2017](https://www.phpconference.nl/)_ 17 | 18 | ## Running a (read: just one) web application in Docker is easy. 19 | 20 | So you're developing or running web applications in Docker but how do you expose them to the outside world? 21 | 22 | When you have just one application the most common option when you're starting with Docker is to just bind a port to the host the container runs on like: 23 | 24 | ```bash 25 | docker run -p 80:80 some/image 26 | ``` 27 | 28 | Now you can reach the application by `http://localhost`. 29 | That would work for development or even for production if you have just one web service per host. 30 | 31 | ## But what if you want more? 32 | 33 | - You want run multiple web services? 34 | - You want to run multiple instances of a service? 35 | - You want to run services on multiple hosts? 36 | - You just want a more descriptive hostname than 'http://localhost'? 37 | 38 | ## Reverse proxy to the rescue! 39 | 40 | A very nice approach to these problems is to use a [reverse proxy](https://en.wikipedia.org/wiki/Proxy_server#Reverse_proxies). 41 | Reverse proxy servers have existed for ages, their job is to distribute incoming traffic (so called ingress) to application(s). 42 | 43 | So you might have heard or even use [Nginx](https://nginx.org/) or [HAProxy](http://www.haproxy.org/) who have been serving the web for quite a while. 44 | 45 | However since we're dealing with containers now there is one big difference: 46 | In comparison to 'classic' servers/virtual machines containers tend to be very volatile. 47 | Each time a service is deployed or scaled new containers come and old ones go. 48 | The proxy needs to be able to keep track of all these changes which means it's not possible to rely on manual configuration. 49 | Instead some automated reconfiguration is required. 50 | 51 | ## Enter Træfɪk 52 | 53 | [Træfɪk](https://traefik.io/) is an proxy that does just that. 54 | It reconfigures itself continuously by listening to events on the system the containers are running on. 55 | When using Docker (Swarm) it listens to the Docker socket (`/var/run/docker.sock`) 56 | 57 | When a container is started it will be automatically be accesible via Træfɪk. 58 | When a container stops (either intended or unintended) it will be removed from the proxy config again. 59 | 60 | You can now let Træfɪk listen to a given (number of) port number(s) like `80` or `443` and let it decided which traffic should go to which containers based on hostname. 61 | Containers as in plural? yes it does load balancing! It can also do SSL termination, maybe more on that later. 62 | 63 | I can only say this is absolutely _awesome_. 64 | I'm using it for quite a while now and I'm still wondering how I could work without it. 65 | And yes there are other products too but this is so simple yet so powerful. 66 | 67 | ## How does Træfɪk work? 68 | 69 | So I said Træfɪk configures itself automatically?, Well almost. 70 | You have to add some configuration to your application to help Træfɪk understand how it should proxy requests to it. 71 | To be able to do this you'll need to understand the basics of how Træfɪk works. 72 | Træfɪk works with the concepts frontend and backend and makes sure traffic from a given frontend (accessible from the web) goes to a given backend (running on the orchestrator). 73 | 74 | ![Træfɪk architecture](images/blog/software/traefik-architecture.png) 75 | 76 | _^Image is courtesy of traefik.io_ 77 | 78 | ## Configuring Træfɪk 79 | Træfɪk can be configured via labels, these labels can be set either as part of the container image or by an orchestration tool. 80 | I prefer the latter since I like decoupling in general because it allows different settings for different environments. 81 | 82 | At minimum Træfɪk needs to know the following things of your web application: 83 | 84 | - To which backend it belongs 85 | - On which domain it should be reachable from outside 86 | - On which port it is running 87 | - Via which network it can be reached 88 | 89 | Furthermore I choose to explicitly enable services for Træfɪk. 90 | This way Træfɪk is not bothered by containers I don't want to expose like backend applications and databases. 91 | Also this keeps the UI a lot cleaner. 92 | 93 | Configuration is done by setting labels on the container. 94 | 95 | A minimum configuration looks like this: 96 | 97 | _Note 1: In the examples below I use `docker-compose` but Træfɪk supports many other systems too._ 98 | 99 | _Note 2: I left out all the usual stuff_ 100 | 101 | ```yaml 102 | version: '2' 103 | 104 | networks: 105 | traefik: 106 | external: 107 | name: traefik_webgateway 108 | 109 | services: 110 | app: 111 | ... 112 | networks: 113 | - traefik 114 | labels: 115 | - "traefik.enable=true" 116 | - "traefik.backend=lucasvanlierop-web" 117 | - "traefik.frontend.rule=Host:lucasvanlierop.nl.localhost" 118 | - "traefik.port=80" 119 | - "traefik.docker.network=traefik_webgateway" 120 | ``` 121 | 122 | For a more complete example see the [`docker-compose.yml` of this site](https://github.com/lucasvanlierop/website/blob/e0f9d60bdfda1adbba7f41077df9870d57860688/docker-compose.yml) 123 | 124 | _Note if you use Docker Compose files to deploy to a Docker Swarm Cluster the `labels` configuration goes under `deploy` rather than directly under the service._ 125 | 126 | ```yaml 127 | version: '3' 128 | 129 | networks: 130 | traefik: 131 | external: 132 | name: traefik_webgateway 133 | 134 | services: 135 | app: 136 | ... 137 | networks: 138 | - traefik 139 | deploy: 140 | labels: 141 | - "traefik.enable=true" 142 | - "traefik.backend=lucasvanlierop-web" 143 | - "traefik.frontend.rule=Host:lucasvanlierop.nl.localhost" 144 | - "traefik.port=80" 145 | - "traefik.docker.network=traefik_webgateway" 146 | ``` 147 | 148 | For the sake of completeness an example of how you could configure this directly in a `Dockerfile` 149 | ```dockerfile 150 | LABEL "traefik.enable=true" \ 151 | "traefik.backend=lucasvanlierop-web" \ 152 | "traefik.frontend.rule=Host:lucasvanlierop.nl.localhost" \ 153 | "traefik.port=80" \ 154 | "traefik.docker.network=traefik_webgateway" 155 | ``` 156 | 157 | ## Running Træfɪk 158 | While Træfɪk itself is a Go binary you - off course - run it as a Docker container. 159 | There's even an [example Docker Compose configuration](https://docs.traefik.io/#docker). 160 | 161 | Note that I prefer to explicitly enable services to be proxies by Træfɪk rather than having it autodetect all containers. 162 | This can be achieved by running it with `--docker.exposedbydefault=false`. 163 | 164 | For all other options: Træfɪk has pretty good [documentation](https://docs.traefik.io/) 165 | 166 | ## And now a quick look at the user interface 167 | 168 | The user interface is pretty basic but shows the info you need about front and back ends, hosts, http protocols, load balancing protocols etc. 169 | 170 | _Note it also has a tab where you can get some stats about application health but that's out of the scope of this article_ 171 | 172 | ![Træfɪk services page](images/blog/software/traefik-services.png) 173 | 174 | ## And now try it yourself! 175 | 176 | I hope Træfɪk will be as valuable to you as it is to me, let me know how it worked out for you! 177 | 178 | If you want to need more, take a look at the [documentation](https://docs.traefik.io/). 179 | 180 | -------------------------------------------------------------------------------- /source/_posts/2017-06-28-running-cli-tools-in-docker-part-1-composer.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Running CLI tools In Docker Part 1; Composer 3 | categories: 4 | software-development 5 | tags: 6 | - docker 7 | - docker-festival 8 | image: /images/blog/software/composer-in-docker.png 9 | imageAlt: Composer logo with containers in the background 10 | --- 11 | 12 | _This article is part of a [series of blog posts](/blog/tags/docker-festival/) related to the 13 | [Docker Festival](https://twitter.com/hashtag/dockerfestival?src=hash) workshop 14 | [Matthias Noback](https://twitter.com/matthiasnoback) and I did at 15 | [Dutch PHP Conference 2017](https://www.phpconference.nl/)_ 16 | 17 | ## When your (web) application runs in a container but tools still run on your host 18 | 19 | I've encountered 'hybrid' combinations where people run their (web) applications in a container but still run tools directly on their host. 20 | 21 | There are however good reasons to run all tools in a container too: 22 | 23 | - It guarantees the tools run on the __exact same software__ as the application itself. 24 | - It guarantees all users of the use the __exact same software__ 25 | - It does __not require installing extra software__ on your host 26 | 27 | In this post the [PHP package manager Composer](https://getcomposer.org/) will be taken as an example on how to run a CLI tool in a container. 28 | Composer was chosen since I have used it for almost every project the last 5 years. 29 | Furthermore since it has quite a few requirements which makes it an interesting case. 30 | 31 | ## There are a few things you need to understand about running processes in Docker. 32 | 33 | ### Processes in Docker run as root by default! 34 | If that does not ring a bell yet: It's advisable to run processes as a non-root user especially when they generate files. 35 | 36 | __This might sound like a contradiction but by default a user would create files that are NOT owned by that user! It would even require sudo rights to remove those files.__ 37 | 38 | ### Some processes [can't be stopped](https://www.youtube.com/watch?v=lP4Nnek6DCo) 39 | The first process that is started in Docker is considered (by Linux) the [init](https://en.wikipedia.org/wiki/Init) process. 40 | Linux is designed to keep that process running whatever happens. 41 | The only way to stop that process (aside from letting Docker `kill` that container) is to let the process itself listen to so called ['signals'](https://en.wikipedia.org/wiki/Unix_signal). 42 | 43 | Most non 'server' processes are not designed to handle signals properly. 44 | In practice this means that you can't stop a process once you've started it. 45 | 46 | This can be resolved by: 47 | 48 | - Declaring a [`STOPSIGNAL`](https://docs.docker.com/engine/reference/builder/#stopsignal) in your `Dockerfile` 49 | - Using an init process such as [Tini](https://github.com/krallin/tini) 50 | - Wrapping the command in a [bash `trap`](http://redsymbol.net/articles/bash-exit-traps/) 51 | 52 | I have personally used Tini a lot since that also solved the [Zombie reaper problem](https://blog.phusion.nl/2015/01/20/docker-and-the-pid-1-zombie-reaping-problem/). 53 | If I remember correctly this problem has been solved by now. 54 | 55 | ### (Most) processes expect configuration 56 | Most processes expect some form of configuration either in the form of files or environment variables. 57 | By default processes running in a container do not have access to anything outside the container either fail or behave different than expected. 58 | 59 | Composer for example expects a `.composer` dir to be present in the home dir (which you can specify). 60 | Since this directory not only contains configuration but also cache files there's an enormous speed benefit in 'reusing' that dir for the container. 61 | 62 | ### Some processes expect devices to be present 63 | Just like variables of file systems (almost) no devices are shared with the container. 64 | 65 | In the case of Composer chances are it expects a SSH socket to be present. 66 | As mentioned earlier it's very likely Composer has to download some GIT repositories which is often done via SSH. 67 | 68 | That's a bit of a problem since that would sharing the SSH socket with the container. 69 | While that's possible it's a bit more work. 70 | Also sharing power of SSH with an isolated process which only needs a access a limited set of resources does not fit the 'least access principle' of containerized processes. 71 | 72 | An easier approach in many cases is to use HTTP(S) instead. 73 | In case access to private repositories is required make sure an access token is set up: 74 | 75 | For GitHub this can be done like: 76 | ```bash 77 | composer config -g github-oauth.github.com 78 | ``` 79 | 80 | See also the [Composer docs on this](https://getcomposer.org/doc/articles/troubleshooting.md#api-rate-limit-and-oauth-tokens) 81 | 82 | For GitLab this can be done like: 83 | ```bash 84 | composer config your.gitlab.domain 85 | ``` 86 | 87 | ## Now lets's containerize Composer 88 | 89 | With that out of the way let's see what is required to run Composer in a container 90 | 91 | ### Install 92 | 93 | First install Composer itself. 94 | Since most of the dependencies Composer is likely to install are either in zip or git 'format' support for both is required too. 95 | 96 | ```dockerfile 97 | # Note this assumes you have already create a base image for you application containing 98 | # PHP and all required libraries. 99 | # In this case the base image is expected to be based on Alpine Linux. 100 | FROM your/application:base 101 | 102 | RUN apk update \ 103 | 104 | # Install init system for running tasks as pid 1 105 | && apk add --no-cache tini \ 106 | 107 | # Install Composer + Git + Zip so it can fetch from various sources 108 | && apk add \ 109 | --no-cache \ 110 | git \ 111 | zlib-dev \ 112 | && php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \ 113 | && php composer-setup.php \ 114 | && php -r "unlink('composer-setup.php');" \ 115 | && chmod +x composer.phar \ 116 | && mv composer.phar /usr/local/bin/composer 117 | 118 | # Run tini as init process 119 | ENTRYPOINT ["/sbin/tini", "--"] 120 | ``` 121 | 122 | ### Configure 123 | 124 | Then share the required configuration (in `docker-compose` format). 125 | The following configuration will cause composer to: 126 | 127 | - Run as a normal user (it's possible to automate by exporting these values using the `id` program) 128 | - Look for it's configuration in /home/.composer 129 | - Run all commands in the project dir that has been shared with the container 130 | 131 | _Note: I left out all the usual stuff_ 132 | 133 | ```yaml 134 | services: 135 | app: 136 | ... 137 | user: user-id:group-id 138 | working_dir: /your-project-dir 139 | environment: 140 | HOME: /home 141 | volumes: 142 | - ./:/your-project-dir 143 | - ~/.composer:/home/.composer 144 | ``` 145 | 146 | ### Run 147 | 148 | Now it's time to run the process. 149 | To prevent having to use docker commands all the time it's nice to have a wrapper script. 150 | The example below runs composer and passes all scripts arguments on to composer. 151 | 152 | ```bash 153 | #!/usr/bin/env bash 154 | docker-compose run --rm app composer "$@" 155 | ``` 156 | 157 | You could run the above like for example: 158 | 159 | ```bash 160 | ./composer install --ansi --no-dev --optimize-autoloader 161 | ``` 162 | 163 | ## Protip: Add platform requirements 164 | Since compose now runs on the same stack (Composer calls it 'platform') as the application it's possible to add specific requirements of the stack itself 165 | without the risk of false positives or negatives. 166 | For example you can require a specific PHP version or extensions to be installed like: 167 | 168 | ```bash 169 | ./composer require "ext-zip" "*" 170 | ``` 171 | 172 | ## Recap 173 | Running CLI tools in Docker is pretty doable but you have to be aware of certain pitfalls. 174 | 175 | I hope I have encouraged you to run your developer tools on the same stack your application runs on. 176 | -------------------------------------------------------------------------------- /source/_posts/2017-12-31-truly-immutable-deployments-with-docker-or-kubernetes.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Truly Immutable deployments with Docker or Kubernetes. 3 | categories: 4 | - software-development 5 | tags: 6 | - docker 7 | - docker-compose 8 | - docker-festival 9 | - immutability 10 | - kubernetes 11 | - security 12 | image: /images/blog/software/locked-door.jpg 13 | imageAlt: "Just a random picture of a locked door" 14 | --- 15 | 16 | __Target audience:__ developers who (are planning to) run containers in production. 17 | 18 | TL;DR Deploy your containers with an immutable file system, its more secure and predictable! 19 | 20 | --- 21 | 22 | ## Deploying should be boring a.k.a. predictable, what does that look like? 23 | 24 | For me boring means it's predictable, which means the deployment process provides some guarantees, such as: 25 | 26 | - it's obvious which code has been deployed *(e.g. can be related to a commit in version control)* 27 | - deployed code behaves as expected *(asserted by tests)* 28 | - deployed code is fully compatible with the stack it runs on *(asserted by tests)*. 29 | - all of the above do not change once deployed 30 | 31 | For now I'd like to focus on the last item, I'll probably write an article on setting up an automated build and test pipeline later. 32 | 33 | ## How predictable are deployment processes nowadays? 34 | 35 | Deployment practices have come along way since 'the old days' where developers used to edit (hack) files directly on servers without any form of testing. 36 | 37 | In today's container era all of above guarantees can be provided by CI pipelines that build, test and deploy code + infrastructure automatically from version control. 38 | This does not mean they ARE always provided, in fact many deployment processes guarantee: 39 | 40 | - it's obvious which code has been deployed 41 | - deployed code behaves as expected 42 | - deployed code is fully compatible with the stack it runs on. 43 | 44 | But still don't guarantee: 45 | 46 | - all of the above do not change once deployed 47 | 48 | ## How to make a deployment even more predictable with immutable containers 49 | 50 | Let's take a look a some reasons that can cause changes in code or configuration after a deployment. 51 | 52 | - Hacking attempts that change the application or even download scripts 53 | - Bugs in the application that write files to an incorrect path 54 | - Frameworks that generate code for caching purposes (common practice with non compiled languages) 55 | - Automatic updates 56 | 57 | Even if some of these changes are desired, they are not tested and therefore unpredictable. Any guarantees provided by the build/test/deployment process can no longer be trusted. If possible these should be part of the build process rather than happen after deployment. Especially updates since they would be gone when a new version is deployed anyway. 58 | 59 | The good news is that changes can be prevented fairly easy by just starting the container with an immutable (read only) file system. 60 | In practice this means an application, its configuration and basically everything else that's in the container can NOT be changed. 61 | Let's see some examples on how to configure this in Docker & Kubernetes. 62 | 63 | ## Example #1 Configuring immutable containers in Docker Compose (version 3) and Docker Swarm 64 | 65 | With Docker all you have to do is mark the service as 'read only'. 66 | 67 | *Note that this example is for version 3 of the Docker Compose file format which can also be used for deploying to Docker Swarm. 68 | For the 'classic' Docker Compose version 2 format check the example below.* 69 | 70 | 71 | ```yaml 72 | version: '3' 73 | 74 | services: 75 | app: 76 | ... 77 | read_only: true 78 | ``` 79 | 80 | The example above assumes the application does not require any changes to the file system. 81 | In practice that might not always be possible because some processes require some files or directories to be writable. 82 | For example a temporary directory for storing file uploads might be necessary. 83 | Of course the final uploaded files should be stored in a persistent volume. 84 | Another example might be processes like Apache HTTP server that want to store their process id in a [`pid` file](https://linux.die.net/man/3/pidfile). 85 | 86 | This of course could be solved by mounting a writable file or directory from the local file system into the container. 87 | However since there's no need to persist these files they can be written to memory instead of disk. 88 | 89 | Docker Compose supports temporary volumes in memory. 90 | In this example a writable directory named `/var/run` will be available in the container. 91 | *Note: it goes without saying that these kind of exclusions should be used as little as possible!* 92 | 93 | ```yaml 94 | version: '3' 95 | 96 | volumes: 97 | apache-run: 98 | driver: local 99 | driver_opts: 100 | type: tmpfs 101 | device: tmpfs 102 | 103 | services: 104 | app: 105 | ... 106 | read_only: true 107 | volumes: 108 | - apache-run:/var/run 109 | ``` 110 | 111 | ## Example #2 Configuring immutable containers in the classic Docker Compose version 2 format 112 | 113 | The Docker Compose version `2` file format is a bit different and has a separate `tmpfs` configuration that creates a temporary volume in memory. 114 | 115 | *Note you have to specify at least the `uid` (user id) that should own the volume (unless the process runs as root which is not recommended for security reasons!).* 116 | 117 | 118 | ```yaml 119 | version: "2" 120 | 121 | app: 122 | read_only: true 123 | tmpfs: 124 | - /var/run:uid={uid-of-the-process} 125 | ``` 126 | 127 | ## Example #3 Configuring immutable file systems in Kubernetes (v1.7+) 128 | 129 | In Kubernetes an immutable file system can be configured as part of a deployment security context. 130 | Instead of Docker's tmpfs a volume of the type `emptyDir` must be configured. 131 | 132 | *Note: as Slava points out in the comments Kubernetes even supports [enforcing the use of read only file systems](https://kubernetes.io/docs/concepts/policy/pod-security-policy/#volumes-and-file-systems)* 133 | 134 | ```yaml 135 | apiVersion: apps/v1beta1 136 | kind: Deployment 137 | metadata: 138 | ... 139 | spec: 140 | ... 141 | template: 142 | ... 143 | spec: 144 | containers: 145 | - name: app 146 | ... 147 | securityContext: 148 | readOnlyRootFilesystem: true 149 | volumeMounts: 150 | - mountPath: /var/run 151 | name: apache-run 152 | volumes: 153 | - name: apache-run 154 | emptyDir: {} 155 | 156 | ``` 157 | 158 | ## Rounding up 159 | 160 | - *Do all containers need to have an immutable file system?* 161 | Of course this is up to you. I would recommend that at least containers where scripts/binaries can be executed should be immutable. So web and application servers and maybe even database servers. 162 | 163 | - *Should development containers have an immutable file system too?* 164 | Not necessarily since it's a mainly security measure for production situations. 165 | However it's better to have a similar setup across all environments so possible issues can be spotted before going to production. 166 | 167 | 168 | *FYI: The examples above are taken from a legacy WordPress project. Since I'm not a WordPress expert at all I intentionally run it in an immutable container to reduce the attack vector and 169 | to prevent unplanned automatic updates from happening. For some more context check the full Docker Compose config at the time of writing for both 170 | [development](https://github.com/allihoppa/allihoppa.nl/blob/4e061496f8d489a00c0d1cf32725d90e376eb426/environment/dev/docker-compose.yml#L28) and 171 | [production](https://github.com/allihoppa/allihoppa.nl/blob/4e061496f8d489a00c0d1cf32725d90e376eb426/environment/prod/docker-compose.yml#L43)* 172 | 173 | --- 174 | 175 | *Thanks 176 | [Jeroen](https://twitter.com/n0x13), 177 | [Annelies](https://twitter.com/alli_hoppa) and 178 | [Caroline](https://twitter.com/erzitkaktussen) 179 | for reviewing this post* 180 | -------------------------------------------------------------------------------- /source/expertise.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: "default" 3 | name: "Lucas van Lierop's Expertise" 4 | --- 5 | 6 |
7 | 8 |

9 | My expertise 10 |

11 | 12 |
    13 |
  • 14 |

    15 | 16 | PHP 17 |

    18 |
      19 |
    • 20 | Frameworks: Symfony Framework 2/3, Zend Framework 1, Sculpin 21 | 22 |
    • 23 |
    • 24 | Unit, integration and functional testing: PHPunit and Behat 25 | 26 |
    • 27 |
    • 28 | Quality inspection: PHPMD, PHPCS, PHPlint 29 | 30 |
    • 31 |
    • 32 | Vulnerability checking: Sensiolabs Security Checker 33 | 34 |
    • 35 |
    • 36 | Package management: Composer 37 |
    • 38 |
    39 |
  • 40 |
  • 41 |

    42 | 43 | JavaScript 44 |

    45 |
      46 |
    • 47 | Frameworks: Backbone, Marionette 48 | 49 |
    • 50 |
    • 51 | Libraries: RequireJS, JQuery, UnderscoreJs, Wreqr 52 | 53 |
    • 54 |
    • 55 | Task runners & build systems like: Gulp, Grunt 56 |
    • 57 | 58 |
    • 59 | Quality inspection with JSHint 60 | 61 |
    • 62 |
    • 63 | Package managers: NPM, Bower 64 |
    • 65 |
    66 |
  • 67 | 68 |
  • 69 |

    70 | 71 | Other languages 72 |

    73 |
      74 |
    • 75 | Data formats: XML/Markdown/Json 76 |
    • 77 |
    • 78 | I have also used Actionscript, Cobol, Perl and VB script. 79 |
    • 80 |
    • 81 | I’m currently learning Scala 82 |
    • 83 |
    84 |
  • 85 | 86 |
  • 87 |

    88 | 89 | Development methodologies & concepts 90 |

    91 |
      92 |
    • 93 | Agile/Scrum 94 |
    • 95 |
    • 96 | Object oriented programming 97 |
    • 98 |
    • 99 | Functional programming 100 |
    • 101 |
    • 102 | Hexagonal architecture 103 | 104 |
    • 105 |
    • 106 | SOLID design 107 |
    • 108 |
    • 109 | TDD 110 | 111 |
    • 112 |
    • 113 | BDD 114 | 115 |
    • 116 |
    • 117 | DDD 118 | 119 |
    • 120 |
    • 121 | CQS 122 | 123 |
    • 124 |
    • 125 | Studying CQRS & event sourcing. 126 |
    • 127 |
    128 |
  • 129 | 130 |
  • 131 |

    132 | 133 | Hosting, Deployment & Provisioning automation 134 |

    135 |
      136 |
    • Docker, Docker Compose
    • 137 |
    • DC/OS, Marathon
    • 138 |
    • Ansible
    • 139 |
    • Vagrant
    • 140 |
    • VirtualBox
    • 141 |
    • DigitalOcean
    • 142 |
    143 |
  • 144 | 145 |
  • 146 |

    147 | 148 | Authentication 149 |

    150 |
      151 |
    • HTTP
    • 152 |
    • OAuth 2
    • 153 |
    • SAML
    • 154 |
    155 |
  • 156 | 157 |
  • 158 |

    159 | 160 | Front-end 161 |

    162 |
      163 |
    • HTML
    • 164 |
    • CSS/Less/Sass
    • 165 |
    • Twitter Bootstrap
    • 166 |
    • Gimp
    • 167 |
    168 |
  • 169 |
  • 170 |

    171 | 172 | Quality Assurance and build tools 173 |

    174 |
      175 |
    • Travis CI
    • 176 |
    • Bamboo
    • 177 |
    • Ant
    • 178 |
    • Make
    • 179 |
    180 |
  • 181 | 182 |
  • 183 |

    184 | 185 | Databases, persistence 186 |

    187 |
      188 |
    • MySQL
    • 189 |
    • Redis
    • 190 |
    191 |
  • 192 | 193 |
  • 194 |

    195 | 196 | Development & productivity tools 197 |

    198 |
      199 |
    • PHPStorm
    • 200 |
    • Git
    • 201 |
    • SVN
    • 202 |
    • Slack
    • 203 |
    • Jira
    • 204 |
    • Trello
    • 205 |
    • GitHub
    • 206 |
    207 |
  • 208 | 209 |
  • 210 |

    211 | 212 | Messaging & job queues 213 |

    214 |
      215 |
    • RabbitMQ
    • 216 |
    • Beanstalk
    • 217 |
    218 |
  • 219 | 220 |
  • 221 |

    222 | 223 | Certificates 224 |

    225 |
      226 |
    • ZCE
    • 227 |
    228 |
  • 229 | 230 |
  • 231 |

    232 | 233 | Human Languages 234 |

    235 |
      236 |
    • Dutch
    • 237 |
    • English
    • 238 |
    239 |
  • 240 | 241 |
  • 242 |

    243 | 244 | HTTP Servers 245 |

    246 |
      247 |
    • Apache
    • 248 |
    • Nginx
    • 249 |
    • HAProxy
    • 250 |
    251 |
  • 252 | 253 |
  • 254 |

    255 | 256 | System Administration 257 |

    258 |
      259 |
    • 260 | Linux Alpine/Ubuntu/Debian/Centos 261 | (I know my way around in Linux, I’m containerizing my desktop apps for fun.) 262 |
    • 263 |
    • Windows
    • 264 |
    • FreeBSD
    • 265 |
    • Bash
    • 266 |
    267 |
  • 268 | 269 |
  • 270 |

    271 | 272 | Event organizing & public speaking 273 |

    274 |

    275 | Besides being an engineer I'm also organizer of DomCode, a non-profit foundation with 1200+ 276 | members that organizes events like the annual DomCode conference and 277 | monthly DomCode meetups where (inter)national speakers share their knowledge on 278 | software development. 279 | Since I value knowledge sharing a lot I became an occasional speaker myself. 280 |

    281 |
  • 282 | 283 |
  • 284 |

    285 | 286 | None work related expertise 287 |

    288 |
      289 |
    • Caring for my family
    • 290 |
    • Event organizing
    • 291 |
    • General construction work
    • 292 |
    • MIG welding
    • 293 |
    • Car maintenance
    • 294 |
    • Electronics
    • 295 |
    296 |
  • 297 |
298 |
299 | -------------------------------------------------------------------------------- /source/_posts/draft-car-hot-dip-galvanizing.md: -------------------------------------------------------------------------------- 1 | --- 2 | draft: true 3 | title: Galvanize car parts, a manual 4 | categories: 5 | - cars 6 | tags: 7 | - hot dip galvanizing 8 | - cars 9 | - restoration 10 | --- 11 | This manual is based on [an article published earlier on a Land Rover forum](http://www.laroprik.nl/viewtopic.php?f=3&t=503973) 12 | --- 13 | 14 | Since I've have seen many questions regarding hot dip galvanizing of chassis and body parts I've 15 | 16 | Aangezien er regelmatig vragen over thermisch verzinken komen dacht ik waarom niet een handleiding maken voor mensen die er aan willen beginnen. Onderstaand een stuk uit een mail die ik ooit eens gestuurd heb naar iemand (ook weer deels op basis van info van Andrew Colville) die mij weer op weg geholpen heeft. Wie helpt er even mee hier een compleet verhaal van te maken wat we ergens centraal kunnen plaatsen (eigenlijk zou ik toch graag weer een Land Rover wiki willen) 17 | Ontlakken: 18 | Veel mensen laten het stralen, ik ben van mening dat dat niet goed genoeg werkt, aan de buitenkant ziet het er keurig uit maar aan de binnenkant kan het zo vol het tectyl, verf, zand etc zitten en daar hecht geen zink op, zal je zien dat het juist van binnenuit weer gaat roesten, zonde van je geld dus. En ja het gaat door een aantal baden maar die zijn niet bedoeld om dat er allemaal uit te krijgen. Ik heb daarom mijn chassis thermisch laten ontlakken en daarna met compressor het resterende (droge) vuil er uit geblazen. 19 | Gaten boren: 20 | Tip wees niet zuinig boor gewoon overal waar lucht er uit moet / zink er in moet gaten en ook groot genoeg. Alle verhalen over kromtrekken zijn overdreven maar het is wel zaak dat het zink snel genoeg door kan lopen. Denk ook niet “ach dit kleine hoekje, dat zal toch wel gaan?” bij mij bleek zink achter in bijvoorbeeld de veerhand en de outriggers. Afgezien van dat dit geld kost en gewicht verhoogt is het soms ook heel onhandig omdat iets niet meer past (in mijn geval o.a. een schroefveer…). 21 | Neem als stelregel dat alles wat een 3 dimensionale vorm heeft doorboord moet worden dus komen er 3 stukken plaat bij elkaar in een hoek of maakt het chassis een bolling, boor er een gat in! Verder nog overal boren waar stukken plaat doorgelast op gelkaar zitten, denk dan aan dwarsbalken in je chassis, dat was in ieder geval bij mij zo. Er zal namelijk altijd, lucht, vuil etc tussen de 2 stukken zitten en dat moet weg kunnen. Bij mij zag je naderhand op dat soort plekken echt klonten donkere prut, waarvan ik gok dat het verroest staal en zink is die tussen de dubbelingen 'uitgeploft is. Dit was bij de body meer dan bij het chassis dus bij jou wellicht bij het schutbord. 22 | Neem flink tijd voor het gaten boren (uren werk!), koop goede harde boren, koel ze met olie, boor netjes van klein naar groot en loop stukje voor stukje je chassis na of het zink overal door kan stromen. Als het zink ergens niet kan komen is dat zonde van de investering en bovendien gevaarlijk want de lucht die opgesloten raakt zet enorm uit door de hitte en zal het chassis doen exploderen. 23 | Achteraf schilderen: 24 | Ga je het ook nog schilderen (zink roest niet maar lost langzaam op) zoja bedenk dan op voorhand wat/hoe je er op wilt smeren, de meeste dingen die op staal hechten doen dat niet op zink, bovendien moet je het ook serieus opschuren of laten aanstralen en in de primer zetten. Poedercoating wordt door veel mensen afgeraden. Ik heb zelf uiteindelijk Brantho Korrux gekozen puur omdat ik dat bij lage temperatuur kan aanbrengen. 25 | Laat je niet bang maken: 26 | Bovenstaande is niet om je bang te maken, dat zullen anderen wel doen. Dan bedoel ik mensen die liever tectyleren of de verzinkerij zelf die zal zeggen dat het kromtrekt. Nee het is bedoeld om je te motiveren er echt iets moois van te maken. 27 | Groet 28 | Rapporteren 29 | Anonymous avatar 30 | Justus Christus 31 | 21-02-2013 11:00 32 | Hoi Lucas, 33 | Prima post! 34 | Erg handig. 35 | Heb je ook tips voor bijvoorbeeld een schutbord? 36 | Mogelijk kunnen we een keer een paar fotos ritselen met de benodigde gaten zichtbaar! 37 | Groet, 38 | Justus! 39 | Rapporteren 40 | Anonymous avatar 41 | Lucas van Lierop 42 | 21-02-2013 11:17 43 | Hoi Justus, 44 | Ik heb geen los schutbord zoals een Def of een Serie maar er zijn wellicht mensen die daar foto's van hebben. Echter als je deze poster volgt zit je altijd goed. 45 | Succes 46 | Rapporteren 47 | Anonymous avatar 48 | Geert Oude Luttighuis 49 | 21-02-2013 11:27 50 | Lucas, mooie aanzet om vragen vóór te zijn/blijven. Toch ook een vraag nav je post: heb je tips voor het thermisch ontlakken? Of is dit in één run gegaan bij je verzinkerij? 51 | Rapporteren 52 | Anonymous avatar 53 | Lucas van Lierop 54 | 21-02-2013 11:36 55 | Ik heb eigenlijk maar een tip, zorg dat ze NIET je loodjes er af knippen omdat ze ‘denken’ dat die ‘toch smelten’. Verder is het gewoon een kwestie van afleveren en ophalen. Ik heb het 1,5 jaar voor het verzinken gedaan zodat ik daar tussenin fijn kon lassen. 56 | Gr 57 | Rapporteren 58 | Anonymous avatar 59 | Geert Oude Luttighuis 60 | 21-02-2013 11:46 61 | Hahaha, goeie tip ja… Maar ik doelde eigenlijk meer op tips als adressen, kosteninzicht e.d., aangezien ze niet overal zomaar thermisch kunnen ontlakken (een pyrolyse-oven is een behoorlijke investering). 62 | Rapporteren 63 | Anonymous avatar 64 | Lucas van Lierop 65 | 21-02-2013 11:54 66 | Ah ok, Ik ben bij TCI in tilburg geweest, voor een complete carrosserie en chassis was ik daar aardig wat geld kwijt (1700 euro). Wellicht is dat met alleen chassis en of schutbord al stuk goedkoper omdat ze er dan makkelijker andere dingen bij kunnen zetten om de prijs te drukken. De volgende keer zou ik daar beter over onderhandelen… Voordeel was wel dat het daarna superfijn werken was zonder allerlei verf tectyl etc. Ik kon overal zo lassen (wel nog roest wegpoetsen). En het verzinken ging dus ook heel goed omdat het zo mooi kaal was, het verzinken zelf is veel goedkoper dan het ontlakken (bv 1,50 per kilo) 67 | Gr 68 | Rapporteren 69 | Anonymous avatar 70 | Nick 71 | 21-02-2013 12:45 72 | Foto`s van een verzinkt schutbord staan op mijn “mijnalbums” pagina. Achteraf gezien had ik het laswerk mooier willen afwerken, maar dit was toen ter tijd bij wijze van proef, die achteraf érg goed gelukt is. Schutbord is gewoon gemonteerd geworden (+/- 3 jaar geleden) en ziet er nog steeds perfect uit, nergens roest te bekennen. 73 | Als ik een keer tijd heb zal ik een poging doen om hier een stukje over te schrijven. 74 | Rapporteren 75 | Anonymous avatar 76 | Andrew Colville 77 | 22-02-2013 01:09 78 | Lucas, Ik wil even toe voegen dat kromtrekken wel kan, maar niet op een manier waardoor het onbruikbaar wordt, Je chassis en schutbord vouwen zich niet dubbel:D de koets van de Classic die ik gedaan heb had hier en daar wel bolstaand plaatwerk en dat valt mijn inziens onder krom trekken het blijft eigen risico, en de voor behandeling het ontlakken en gaten boren zijn heel belangerijk. 79 | Misschien kun je dat aan je verhaaltje toevoegen? 80 | Verder kun je te verzinken oud staal (geverfd/tectyl etc ) ook laten “Loogen” ontlakken op basis van chemicaliën, ook dit is prijzig €1000 euro voor de koets en chassis destijds ( 2006 uit mijn hoofd ) Maar dan zit er niks meer behalve staal, het staal is helemaal schoon ook tussen de naden en in kokers en plakken waar je niet aan denkt. 81 | je kunst werk begint direct te roesten ( vlieg roest genoemd ) dat is net erg voor het zink procces, zelfs beter voor de aanhechting van het zink, maar minder handig als je buiten werkt aan het project en nog veel moet lassen voor je naar de verzinkerijk kunt, je kunt het roesten tegen gaan door het staal in te vetten, dit vet wordt bij de verzinkerij weer verwijdert in de voor behandelings procces. 82 | de Loog chemicaliën hebben ook geen invloed op het zink, maar wel wanner je direct het staal gaat lakken en niet eerst verzinken, de chemicaliën kunnen je lak opvreten. 83 | Rapporteren 84 | Anonymous avatar 85 | Lucas van Lierop 86 | 22-02-2013 07:40 87 | Dank voor je toeoeging, ik ga er een een mooi verhaal van maken. Bij mij stond er idd ook wel wat bol maar niks zichtbaars. verder ook een stukje krom maar dat is echt mijn schuld omdat ik daar de wing half verbouwd had ivm grotere wielen en een snorkel. Ach ja dat is ook geen punt, voordeel bij land rover is dat je bijna alles met alu plaatwerk afdekt 88 | Rapporteren 89 | 1 90 | 91 | 2 92 | 93 | het gevaar van kromtrekken is inderdaad niet zo groot. Er is wat bol gaan staan inderdaad. maar niet erg. Er was meer beschadigd doordat ze de boel waarschijnlijk hebben laten vallen of tijdens het hijsen tegen elkaar hebben laten stoten. Maar ook dat was geen probleem. 94 | Voor versteviging had ik de punten van het schutbord met een hoeklijn en bouten aan elkaar bevestigd. Helaas had ik daar in mijn onwetendheid bouten voor gebruikt die verzinkt waren. Ik kreeg alles netjes terug in onderdelen. De hoeklijn en het schutbord hadden ze van elkaar af gehaald en afzonderlijk verzinkt. :( Gelukkig was er niets krom geworden. Maar gebruik dus geen verzinkte bouten om steunstrips te bevestigen. 95 | Arnold. 96 | Rapporteren 97 | Anonymous avatar 98 | mirza.denhond@hotmail.com 99 | 01-03-2013 19:39 100 | Hallo. 101 | Ook een goed alternatief voor thermisch verzinken, en ook als alternatief voor electrolytisch verzinken ,is het behandelen met ZINGA. Dit is een coating/verf van bijna pure zink dat koudgalvanisatie noemt. Ik heb destijds, meer dan 20 jaar geleden, delen van mijn 109 schutbord behandeld en het roest komt er nog niet door. Dit produkt dat met borstel of spuitpistool wordt aangebracht hecht op lichte roest, en zelfs op licht vochtige ondergrond. Wel is het best dat het metaal opgeschuurd of gezandstraald is. Op verf heeft het een slechte hechting, maar daarentegen hecht bijna iedere verf op de ZINGA. Op het internet is er wel meer terug te vinden. 102 | Rapporteren 103 | Anonymous avatar 104 | Lucas van Lierop 105 | 02-03-2013 19:32 106 | Afgezien van het feit dat alles wat je erop smeert een alternatief is beschouw ik dit niet echt als een alternatief. Bij thermisch verzinken komt het zink nl. Ook IN de constructie en zelfs voor een deel In de naden. Dit bereik je alleen met dippen in een bad. 107 | Rapporteren 108 | -------------------------------------------------------------------------------- /source/scss/normalize.scss: -------------------------------------------------------------------------------- 1 | /*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /** 4 | * 1. Change the default font family in all browsers (opinionated). 5 | * 2. Correct the line height in all browsers. 6 | * 3. Prevent adjustments of font size after orientation changes in 7 | * IE on Windows Phone and in iOS. 8 | */ 9 | 10 | /* Document 11 | ========================================================================== */ 12 | 13 | html { 14 | font-family: sans-serif; /* 1 */ 15 | line-height: 1.15; /* 2 */ 16 | -ms-text-size-adjust: 100%; /* 3 */ 17 | -webkit-text-size-adjust: 100%; /* 3 */ 18 | } 19 | 20 | /* Sections 21 | ========================================================================== */ 22 | 23 | /** 24 | * Remove the margin in all browsers (opinionated). 25 | */ 26 | 27 | body { 28 | margin: 0; 29 | } 30 | 31 | /** 32 | * Add the correct display in IE 9-. 33 | */ 34 | 35 | article, 36 | aside, 37 | footer, 38 | header, 39 | nav, 40 | section { 41 | display: block; 42 | } 43 | 44 | /** 45 | * Correct the font size and margin on `h1` elements within `section` and 46 | * `article` contexts in Chrome, Firefox, and Safari. 47 | */ 48 | 49 | h1 { 50 | font-size: 2em; 51 | margin: 0.67em 0; 52 | } 53 | 54 | /* Grouping content 55 | ========================================================================== */ 56 | 57 | /** 58 | * Add the correct display in IE 9-. 59 | * 1. Add the correct display in IE. 60 | */ 61 | 62 | figcaption, 63 | figure, 64 | main { /* 1 */ 65 | display: block; 66 | } 67 | 68 | /** 69 | * Add the correct margin in IE 8. 70 | */ 71 | 72 | figure { 73 | margin: 1em 40px; 74 | } 75 | 76 | /** 77 | * 1. Add the correct box sizing in Firefox. 78 | * 2. Show the overflow in Edge and IE. 79 | */ 80 | 81 | hr { 82 | box-sizing: content-box; /* 1 */ 83 | height: 0; /* 1 */ 84 | overflow: visible; /* 2 */ 85 | } 86 | 87 | /** 88 | * 1. Correct the inheritance and scaling of font size in all browsers. 89 | * 2. Correct the odd `em` font sizing in all browsers. 90 | */ 91 | 92 | pre { 93 | font-family: monospace, monospace; /* 1 */ 94 | font-size: 1em; /* 2 */ 95 | } 96 | 97 | /* Text-level semantics 98 | ========================================================================== */ 99 | 100 | /** 101 | * 1. Remove the gray background on active links in IE 10. 102 | * 2. Remove gaps in links underline in iOS 8+ and Safari 8+. 103 | */ 104 | 105 | a { 106 | background-color: transparent; /* 1 */ 107 | -webkit-text-decoration-skip: objects; /* 2 */ 108 | } 109 | 110 | /** 111 | * Remove the outline on focused links when they are also active or hovered 112 | * in all browsers (opinionated). 113 | */ 114 | 115 | a:active, 116 | a:hover { 117 | outline-width: 0; 118 | } 119 | 120 | /** 121 | * 1. Remove the bottom border in Firefox 39-. 122 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 123 | */ 124 | 125 | abbr[title] { 126 | border-bottom: none; /* 1 */ 127 | text-decoration: underline; /* 2 */ 128 | text-decoration: underline dotted; /* 2 */ 129 | } 130 | 131 | /** 132 | * Prevent the duplicate application of `bolder` by the next rule in Safari 6. 133 | */ 134 | 135 | b, 136 | strong { 137 | font-weight: inherit; 138 | } 139 | 140 | /** 141 | * Add the correct font weight in Chrome, Edge, and Safari. 142 | */ 143 | 144 | b, 145 | strong { 146 | font-weight: bolder; 147 | } 148 | 149 | /** 150 | * 1. Correct the inheritance and scaling of font size in all browsers. 151 | * 2. Correct the odd `em` font sizing in all browsers. 152 | */ 153 | 154 | code, 155 | kbd, 156 | samp { 157 | font-family: monospace, monospace; /* 1 */ 158 | font-size: 1em; /* 2 */ 159 | } 160 | 161 | /** 162 | * Add the correct font style in Android 4.3-. 163 | */ 164 | 165 | dfn { 166 | font-style: italic; 167 | } 168 | 169 | /** 170 | * Add the correct background and color in IE 9-. 171 | */ 172 | 173 | mark { 174 | background-color: #ff0; 175 | color: #000; 176 | } 177 | 178 | /** 179 | * Add the correct font size in all browsers. 180 | */ 181 | 182 | small { 183 | font-size: 80%; 184 | } 185 | 186 | /** 187 | * Prevent `sub` and `sup` elements from affecting the line height in 188 | * all browsers. 189 | */ 190 | 191 | sub, 192 | sup { 193 | font-size: 75%; 194 | line-height: 0; 195 | position: relative; 196 | vertical-align: baseline; 197 | } 198 | 199 | sub { 200 | bottom: -0.25em; 201 | } 202 | 203 | sup { 204 | top: -0.5em; 205 | } 206 | 207 | /* Embedded content 208 | ========================================================================== */ 209 | 210 | /** 211 | * Add the correct display in IE 9-. 212 | */ 213 | 214 | audio, 215 | video { 216 | display: inline-block; 217 | } 218 | 219 | /** 220 | * Add the correct display in iOS 4-7. 221 | */ 222 | 223 | audio:not([controls]) { 224 | display: none; 225 | height: 0; 226 | } 227 | 228 | /** 229 | * Remove the border on images inside links in IE 10-. 230 | */ 231 | 232 | img { 233 | border-style: none; 234 | } 235 | 236 | /** 237 | * Hide the overflow in IE. 238 | */ 239 | 240 | svg:not(:root) { 241 | overflow: hidden; 242 | } 243 | 244 | /* Forms 245 | ========================================================================== */ 246 | 247 | /** 248 | * 1. Change the font styles in all browsers (opinionated). 249 | * 2. Remove the margin in Firefox and Safari. 250 | */ 251 | 252 | button, 253 | input, 254 | optgroup, 255 | select, 256 | textarea { 257 | font-family: sans-serif; /* 1 */ 258 | font-size: 100%; /* 1 */ 259 | line-height: 1.15; /* 1 */ 260 | margin: 0; /* 2 */ 261 | } 262 | 263 | /** 264 | * Show the overflow in IE. 265 | * 1. Show the overflow in Edge. 266 | */ 267 | 268 | button, 269 | input { /* 1 */ 270 | overflow: visible; 271 | } 272 | 273 | /** 274 | * Remove the inheritance of text transform in Edge, Firefox, and IE. 275 | * 1. Remove the inheritance of text transform in Firefox. 276 | */ 277 | 278 | button, 279 | select { /* 1 */ 280 | text-transform: none; 281 | } 282 | 283 | /** 284 | * 1. Prevent a WebKit bug where (2) destroys native `audio` and `video` 285 | * controls in Android 4. 286 | * 2. Correct the inability to style clickable types in iOS and Safari. 287 | */ 288 | 289 | button, 290 | html [type="button"], /* 1 */ 291 | [type="reset"], 292 | [type="submit"] { 293 | -webkit-appearance: button; /* 2 */ 294 | } 295 | 296 | /** 297 | * Remove the inner border and padding in Firefox. 298 | */ 299 | 300 | button::-moz-focus-inner, 301 | [type="button"]::-moz-focus-inner, 302 | [type="reset"]::-moz-focus-inner, 303 | [type="submit"]::-moz-focus-inner { 304 | border-style: none; 305 | padding: 0; 306 | } 307 | 308 | /** 309 | * Restore the focus styles unset by the previous rule. 310 | */ 311 | 312 | button:-moz-focusring, 313 | [type="button"]:-moz-focusring, 314 | [type="reset"]:-moz-focusring, 315 | [type="submit"]:-moz-focusring { 316 | outline: 1px dotted ButtonText; 317 | } 318 | 319 | /** 320 | * Change the border, margin, and padding in all browsers (opinionated). 321 | */ 322 | 323 | fieldset { 324 | border: 1px solid #c0c0c0; 325 | margin: 0 2px; 326 | padding: 0.35em 0.625em 0.75em; 327 | } 328 | 329 | /** 330 | * 1. Correct the text wrapping in Edge and IE. 331 | * 2. Correct the color inheritance from `fieldset` elements in IE. 332 | * 3. Remove the padding so developers are not caught out when they zero out 333 | * `fieldset` elements in all browsers. 334 | */ 335 | 336 | legend { 337 | box-sizing: border-box; /* 1 */ 338 | color: inherit; /* 2 */ 339 | display: table; /* 1 */ 340 | max-width: 100%; /* 1 */ 341 | padding: 0; /* 3 */ 342 | white-space: normal; /* 1 */ 343 | } 344 | 345 | /** 346 | * 1. Add the correct display in IE 9-. 347 | * 2. Add the correct vertical alignment in Chrome, Firefox, and Opera. 348 | */ 349 | 350 | progress { 351 | display: inline-block; /* 1 */ 352 | vertical-align: baseline; /* 2 */ 353 | } 354 | 355 | /** 356 | * Remove the default vertical scrollbar in IE. 357 | */ 358 | 359 | textarea { 360 | overflow: auto; 361 | } 362 | 363 | /** 364 | * 1. Add the correct box sizing in IE 10-. 365 | * 2. Remove the padding in IE 10-. 366 | */ 367 | 368 | [type="checkbox"], 369 | [type="radio"] { 370 | box-sizing: border-box; /* 1 */ 371 | padding: 0; /* 2 */ 372 | } 373 | 374 | /** 375 | * Correct the cursor style of increment and decrement buttons in Chrome. 376 | */ 377 | 378 | [type="number"]::-webkit-inner-spin-button, 379 | [type="number"]::-webkit-outer-spin-button { 380 | height: auto; 381 | } 382 | 383 | /** 384 | * 1. Correct the odd appearance in Chrome and Safari. 385 | * 2. Correct the outline style in Safari. 386 | */ 387 | 388 | [type="search"] { 389 | -webkit-appearance: textfield; /* 1 */ 390 | outline-offset: -2px; /* 2 */ 391 | } 392 | 393 | /** 394 | * Remove the inner padding and cancel buttons in Chrome and Safari on macOS. 395 | */ 396 | 397 | [type="search"]::-webkit-search-cancel-button, 398 | [type="search"]::-webkit-search-decoration { 399 | -webkit-appearance: none; 400 | } 401 | 402 | /** 403 | * 1. Correct the inability to style clickable types in iOS and Safari. 404 | * 2. Change font properties to `inherit` in Safari. 405 | */ 406 | 407 | ::-webkit-file-upload-button { 408 | -webkit-appearance: button; /* 1 */ 409 | font: inherit; /* 2 */ 410 | } 411 | 412 | /* Interactive 413 | ========================================================================== */ 414 | 415 | /* 416 | * Add the correct display in IE 9-. 417 | * 1. Add the correct display in Edge, IE, and Firefox. 418 | */ 419 | 420 | details, /* 1 */ 421 | menu { 422 | display: block; 423 | } 424 | 425 | /* 426 | * Add the correct display in all browsers. 427 | */ 428 | 429 | summary { 430 | display: list-item; 431 | } 432 | 433 | /* Scripting 434 | ========================================================================== */ 435 | 436 | /** 437 | * Add the correct display in IE 9-. 438 | */ 439 | 440 | canvas { 441 | display: inline-block; 442 | } 443 | 444 | /** 445 | * Add the correct display in IE. 446 | */ 447 | 448 | template { 449 | display: none; 450 | } 451 | 452 | /* Hidden 453 | ========================================================================== */ 454 | 455 | /** 456 | * Add the correct display in IE 10-. 457 | */ 458 | 459 | [hidden] { 460 | display: none; 461 | } 462 | -------------------------------------------------------------------------------- /source/_posts/2018-02-02-deploying-dockerized-applications-via-an-ssh-tunnel.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Deploying Dockerized applications via an SSH tunnel. 3 | categories: 4 | software-development 5 | tags: 6 | - continuous-integration 7 | - continuous-deployment 8 | - docker 9 | - docker-festival 10 | - docker-swarm 11 | image: /images/blog/software/tunnel.jpg 12 | --- 13 | 14 | __Target audience:__ Developers who want to (continuously) deploy their Dockerized application. 15 | 16 | *TL;DR: SSH can be used as an alternative to deploying Dockerized applications, this works best with Docker Swarm.* 17 | --- 18 | 19 | Many Docker projects start life as a Docker Compose based development setup. 20 | At a given moment these projects need to be deployed to production. 21 | This article will explain an alternative way deploying to a remote Docker server/cluster using the Docker socket and SSH instead of the Docker HTTP API. 22 | 23 | ## Why deploy Dockerized applications over SSH instead of directly via the API? 24 | *Disclaimer this article doesn't say you MUST but you COULD deploy over SSH.* 25 | 26 | Docker offers a secured API which can be used for deployments. 27 | However... not everyone (or one's system administrator) wants to expose yet another port to the outside world. 28 | Also the Docker API requires setting up some TLS certificates. 29 | Docker has a tool named ['Machine'](https://docs.docker.com/machine/) that can manage those certificates however Machine comes with its own configuration which along with the certificates isn't very portable. 30 | 31 | An alternative solution is to use the Docker socket and let 'come to you' over a secure connection that in most cases is already present: SSH. Or more specific: an SSH tunnel. 32 | Since SSH supports public key authentication granting deployment access to a co-worker or CI server is just a matter of copying their public key to the server/cluster. 33 | 34 | By using an [SSH tunnel](https://www.ssh.com/ssh/tunneling/) it's possible to 'trick' Docker by letting it think 35 | it's deploying to a local port which is actually a Docker socket on a remote system. 36 | 37 | ## But should I expose SSH at all? 38 | 39 | 40 | 41 | Well David has a fair point to consider. In this case I suggest NOT to use SSH to make changes to the server 42 | - [#immutabilityFTW](https://twitter.com/search?q=immutabilityFTW&src=typd) - 43 | but ONLY for deployments. 44 | 45 | ## Prerequisites 46 | - A server with [Docker installed](https://docs.docker.com/engine/installation/). 47 | - A Docker Image registry like [Docker Hub](https://hub.docker.com/). 48 | - A way to (preferably an automated CI pipeline) build Docker images and push them to the registry. 49 | *(If you're unsure how to set this up, let me know in the comments. I might write an article about it!).* 50 | - Knowledge of writing [Docker Compose v3 files](https://docs.docker.com/compose/compose-file/) 51 | 52 | ## What needs to be done to deploy Dockerized applications via SSH? 53 | - Define which services should run e.g. in a (Docker stack deploy compatible) `docker-compose.yml` V3 file, which is probably similar to the development setup. 54 | - Define environment specific config either the `environment` section of a `docker-compose.yml` file or in a separate `*.env` file. 55 | - Tell the server to start/update these services with the specified config. 56 | 57 | ## Why Docker Swarm should be used for production rather than Docker Compose 58 | Earlier in this article Docker Swarm is mentioned a few times, why not use Docker Compose? 59 | A common first approach to deploy application to a production server is to use Docker Compose since that is often the tool used during development. 60 | While using Docker Compose for production is possible, Docker has a better alternative built in named: [Swarm](https://docs.docker.com/get-started/part4/). 61 | Swarm - *from a deployment perspective* - works very similar to Docker Compose but has more advanced orchestration capabilities. 62 | 63 | Compared to Docker Compose, Docker Swarm has the following advantages: 64 | 65 | - It doesn't require installing Docker Compose on the server 66 | - It doesn't require files like `docker-compose.yml`, `*.env` to be copied to the remote server 67 | - It supports multiple servers for high availability (or just to provide more resources) 68 | - It supports storing [secrets](https://docs.docker.com/engine/swarm/secrets/) for e.g. credentials that cannot be provided as an environment variable 69 | 70 | Alternatively to Docker Swarm there are of course orchestrators like 71 | [Kubernetes](https://kubernetes.io/) 72 | or [DC/OS Marathon](https://mesosphere.com/blog/marathon-production-ready-containers/) 73 | but Docker Swarm still is the easiest production ready Docker orchestration tool to start with. 74 | 75 | So let's start! 76 | 77 | ## Step 1: preparing a server for use with Docker Swarm 78 | 79 | To converting an existing Docker (1.13+) server into a Swarm node all that needs to be done is run the following command on the server: 80 | ```bash 81 | docker swarm init 82 | ``` 83 | 84 | *Note: When a multi server ('node' in Docker Swarm speak) setup is desired a bit more is required -> read the [docs](https://docs.docker.com/get-started/part4/).* 85 | 86 | 87 | *Note: if containers started by `docker(-compose)` are already running on the host the need to be stopped and restarted via `docker stack deploy`.* 88 | 89 | ## Step 2: setting up a tunnel to the Docker socket 90 | 91 | As explained earlier the goal is to tunnel the remote Docker socket to the local system 92 | There's one caveat though: this only works on SSHv6.7+ while most older OSes are stuck on SSHv6. 93 | However there's no need for updating since you can run an [SSH tunnel in Docker](https://hub.docker.com/r/kingsquare/tunnel/). 94 | 95 | *Note: while it's possible to tunnel a remote __socket__ to a local __socket__ in this example the remote Docker __socket__ is tunneled to a locale __port__. 96 | This prevents having to deal with file permissions of the socket.* 97 | 98 | ```bash 99 | DOCKER_TUNNEL_CONTAINER=docker_swarm_ssh_tunnel 100 | DOCKER_TUNNEL_PORT=12374 101 | DOCKER_SWARM_HOST={public-ip-or-hostname-of-a-swarm-master} 102 | DEPLOY_USER={user-that-can-connect-via-ssh} 103 | 104 | docker run \ 105 | -d \ 106 | --name ${DOCKER_TUNNEL_CONTAINER} \ 107 | -p ${DOCKER_TUNNEL_PORT}:${DOCKER_TUNNEL_PORT} \ 108 | -v ${SSH_AUTH_SOCK}:/ssh-agent \ 109 | kingsquare/tunnel \ 110 | *:${DOCKER_TUNNEL_PORT}:/var/run/docker.sock \ 111 | ${DEPLOY_USER}@${DOCKER_SWARM_HOST} 112 | ``` 113 | 114 | *Note: Above setup is known to work on Debian like distros, 115 | Red Hat like distros do not seem to have a `SSH_AUTH_SOCK` environment variable. 116 | Suggestions to make this work on Red Hat like distros are welcome.* 117 | 118 | ## Step 3: waiting until the tunnel is established 119 | Now the tunnel has been started (in the background) any further commands should wait until it's actually usable. 120 | An easy way to do that is to poll the tunnel by executing a simple docker command like `docker version`. 121 | 122 | ```bash 123 | until docker -H localhost:${DOCKER_TUNNEL_PORT} version 2>/dev/null 1>/dev/null > /dev/null; do 124 | echo "Waiting for docker tunnel"; 125 | sleep 1; 126 | done 127 | ``` 128 | 129 | *Note: if the connection cannot be established for some reason the `until` loop will run forever. 130 | If desired a [`timeout`](https://ss64.com/bash/timeout.html) can be added to stop the polling after a given amount of time. 131 | This requires wrapping the loop in a separate bash script (or make target)* 132 | 133 | ## Step 4: deploying with `docker stack deploy` 134 | 135 | Once a SSH tunnel has been established Docker can use it to deploy the stack to a remote Swarm node: 136 | 137 | ```bash 138 | DOCKER_STACK_FILE={path/to/docker-compose.yml} 139 | DOCKER_STACK_NAME={name-that-will-be-prefixed-to-each-server} 140 | 141 | docker \ 142 | -H localhost:${DOCKER_TUNNEL_PORT} \ 143 | stack deploy \ 144 | --with-registry-auth \ 145 | -c ${DOCKER_STACK_FILE} \ 146 | --prune \ 147 | ${DOCKER_STACK_NAME} 148 | ``` 149 | 150 | ## Step 5: closing the tunnel again 151 | After the deploying has either succeeded or failed the tunnel should be closed again. 152 | 153 | ```bash 154 | docker stop ${DOCKER_TUNNEL_CONTAINER} 155 | docker rm ${DOCKER_TUNNEL_CONTAINER} 156 | ``` 157 | 158 | ## A final overview of the total script 159 | 160 | *Note: this example is a simplified version of the deployment setup of [this website](https://lucasvanlierop.nl/) which relies heavily [GNU Make](https://www.gnu.org/software/make/manual/make.html) for orchestrating testing, building and deploying. 161 | If you're not experience with GNU Make yet please give it a try, especially the [pre requisites](https://www.gnu.org/software/make/manual/html_node/Prerequisite-Types.html) are very powerful once you grasp the concept.* 162 | 163 | ```bash 164 | #!/usr/bin/env bash 165 | 166 | #...docker build, docker login, docker push etc. 167 | 168 | DOCKER_TUNNEL_CONTAINER=docker_swarm_ssh_tunnel 169 | DOCKER_TUNNEL_PORT=12374 170 | DOCKER_SWARM_HOST={public-ip-or-hostname-of-a-swarm-master} 171 | DEPLOY_USER={user-that-can-connect-via-ssh} 172 | DOCKER_STACK_FILE={path/to/docker-compose.yml} 173 | DOCKER_STACK_NAME={name-that-will-be-prefixed-to-each-server} 174 | 175 | docker run \ 176 | -d \ 177 | --name ${DOCKER_TUNNEL_CONTAINER} \ 178 | -p ${DOCKER_TUNNEL_PORT}:${DOCKER_TUNNEL_PORT} \ 179 | -v ${SSH_AUTH_SOCK}:/ssh-agent \ 180 | kingsquare/tunnel \ 181 | *:${DOCKER_TUNNEL_PORT}:/var/run/docker.sock \ 182 | ${DEPLOY_USER}@${DOCKER_SWARM_HOST} 183 | 184 | until docker -H localhost:${DOCKER_TUNNEL_PORT} version 2>/dev/null 1>/dev/null > /dev/null; do 185 | echo "Waiting for docker tunnel"; 186 | sleep 1; 187 | done 188 | 189 | docker \ 190 | -H localhost:${DOCKER_TUNNEL_PORT} \ 191 | stack deploy \ 192 | --with-registry-auth \ 193 | -c ${DOCKER_STACK_FILE} \ 194 | --prune \ 195 | ${DOCKER_STACK_NAME} 196 | 197 | docker stop ${DOCKER_TUNNEL_CONTAINER} 198 | docker rm ${DOCKER_TUNNEL_CONTAINER} 199 | ``` 200 | 201 | Enjoy deploying your applications! 202 | 203 | To see this in more context check [the deploy setup of this site at the time of writing this article](https://github.com/lucasvanlierop/website/blob/ed0483d5f6b12335a735da0e3d8b6859aacfe8f4/Makefile#L112) 204 | 205 | --- 206 | 207 | *Thanks 208 | [Robin](https://twitter.com/fruitl00p) for creating this awesome [Docker SSH Tunnel image](https://hub.docker.com/r/kingsquare/tunnel/) 209 | and [Bram](https://twitter.com/Brammm) for triggering to finally write this article.* 210 | 211 | *Thanks 212 | [Annelies](https://twitter.com/alli_hoppa) and 213 | [Bram](https://twitter.com/Brammm) 214 | for reviewing this post* 215 | -------------------------------------------------------------------------------- /source/images/blog/software/empowered-by-gnu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 50 | 51 | 52 | 53 | 55 | 56 | 58 | Empowered by GNU 59 | A modified version of the original included the text "empowered by" above the GNU Head. 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | Aurelio A. Hecker <aurium@gmail.com>, Shaddy Zeineddine 73 | 74 | 75 | 76 | 77 | Aurelio A. Hecker <aurium@gmail.com>, Shaddy Zeineddine 78 | 79 | 80 | 2008-04-08 81 | image/svg+xml 82 | 84 | 86 | en 87 | 88 | 89 | 97 | 98 | 103 | 105 | 109 | 110 | empowered by --------------------------------------------------------------------------------