Your browser doesn't support the features required by impress.js, so you are presented with a simplified version of this presentation.

For the best experience please use the latest Chrome, Safari or Firefox browser AND enable JavaScript.

Building a Local development Setup for all our projects

Ideally we would want:

  • Consistency
    (among all developers)
  • Proximity
    (to production environment)
  • Lightweight
    (local software > docker > VM)
  • Disposable
    (easy to destroy and replicate)
  • Low Learning Curve
    (shouldn't need to study to start developing)
A talk by Julio Foulquie

WHY?

  • Faster onboarding process
  • Minimum set up documented
    and committed in the project
  • Version consistency
  • Fast set up allows
    for easier context switching
A talk by Julio Foulquie

Options

  • Good old apache (or nginx) + PHP
    Good old Rails server running locally
    Be ready to keep editing local files and installing packages
    (plus version inconsistency)
  • Homestead (vagrant)
    Full blown Ubuntu box plus routing is still missing
  • Laravel Valet
    Mac only 🍎🤮
  • Laradock
    Either you commit it into your projects or keep it out of version control
  • Custom docker config
    It can work but you get problems with exposed ports and hardcoded configs
  • Others
    ¯\_(ツ)_/¯
A talk by Julio Foulquie

My Solution

A talk by Julio Foulquie

docker-compose.yml


                version: "3"
                services:
                
                  # The Application
                  my_app:
                    image: bitnami/rails:6
                    working_dir: /var/www/
                    volumes:
                      - ./:/var/www/
                      - ./resources/docker/vhost.conf:/etc/nginx/conf.d/default.conf:ro
                      - ./resources/docker/xdebug.ini:/usr/local/etc/php/conf.d/20-xdebug.ini:ro
                    environment:
                      - "POSTGRES_USER=postgres"
                      - "POSTGRES_PASSWD=supersecret"
                      - "POSTGRES_HOST=db"
                      - "REDIS_HOST=redis"
                
                    labels:
                      - "traefik.enable=true"
                      - "traefik.http.middlewares.redirect-2-https.redirectscheme.scheme=https"
                      - "traefik.http.routers.insecure-myapp.entrypoints=web"
                      - "traefik.http.routers.insecure-myapp.rule=Host(`myapp.dev.j3j5.uy`)"
                      - "traefik.http.routers.insecure-myapp.middlewares=redirect-2-https@docker"
                      - "traefik.http.routers.myapp.entrypoints=web-secure"
                      - "traefik.http.routers.myapp.rule=Host(`myapp.dev.j3j5.uy`)"
                      - "traefik.http.routers.myapp.tls=true"
                  # The Database
                  database:
                    image: postgres:alpine3.15
                    volumes:
                      - dbdata:/var/lib/postgresql/data
                      - ./resources/docker/sql-dumps/:/docker-entrypoint-initdb.d/:ro
                      - ./resources/docker/percona.cnf:/etc/percona-server.conf.d/custom.cnf:ro
                    environment:
                      - "POSTGRES_PASSWORD=supersecret"
                      - "PGDATA=/var/lib/postgresql/data/pgdata"
                    
                

                elastic:
                  image: docker.elastic.co/elasticsearch/elasticsearch:8.8.1
                  volumes:
                      - esdata:/usr/share/elasticsearch/data
                      - ./resources/docker/jvm.options:/usr/share/elasticsearch/config/jvm.options:ro
                  environment:
                      - cluster.name=my-cluster
                      - node.name=the-node
                      - bootstrap.memory_lock=true
                      - discovery.type=single-node
                      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
                  ulimits:
                    memlock:
                      soft: -1
                      hard: -1
                
                # Redis
                redis:
                  image: redis:7-alpine
                  volumes:
                    - redisdata:/data
                    - ./resources/docker/redis.conf:/usr/local/etc/redis/redis.conf:ro
                
                volumes:
                  dbdata:
                  redisdata:
                  pmadata:
                  esdata:
                
                networks:
                default:
                  external: true
                  name: gateway
                    
                

Docker-Compose commands


                # To start the containers
                docker-compose up -d
                
                # To stop them
                docker-compose down
                
                # To delete them
                docker-compose rm -vsf
                
                # To tail the logs from the container
                docker-compose logs -f database
                
                # To stop the container AND remove the orphaned containers
                docker-compose down -v --remove-orphans
                
                # To get a working shell
                docker-compose run --rm myapp sh
                    
                
TOO CUMBERSOME!
HARD TO REMEMBER!

Makefile


                container=myapp
                container_id=$(shell docker ps --filter "name=$(container)" --format '{{.ID}}')
                compose_file=docker-compose.yml
                host=$(shell grep -m 1 'rule=Host(`.*`)' $(compose_file) | sed 's/[^`]*`\(.*\)`[^`]""/\1/' )
                
                up:
                	docker-compose up -d
                	@echo ""
                	@echo "You can access the project at: https://$(host)"
                	@echo ""
                .PHONY: up
                
                build:
                	docker-compose rm -vsf
                	docker-compose down -v --remove-orphans
                	docker-compose build
                	docker-compose up -d
                	@echo ""
                	@echo "You can access the project at: https://$(host)"
                	@echo ""
                .PHONY: build
                
                clean:
                	docker-compose rm -vsf
                	docker-compose down -v --remove-orphans
                .PHONY: clean
                
                down:
                	docker-compose down --remove-orphans
                .PHONY: down
                
                    
                

                restart:
                	docker-compose down --remove-orphans
                	docker-compose up -d
                	@echo ""
                	@echo "You can access the project at: https://$(host)"
                	@echo "DB Admin: https://$(db_admin_host)"
                	@echo ""
                .PHONY: restart
                
                migrate:
                	docker exec -it $(container_id) php artisan migrate
                .PHONY: migrate
                
                migrate-fresh:
                	docker exec -it $(container_id) php artisan migrate:fresh
                .PHONY: migrate-fresh
                
                migrate-rollback:
                	docker exec -it $(container_id) php artisan migrate:rollback
                .PHONY: migrate-rollback
                
                seed:
                	docker exec -it $(container_id) php artisan db:seed
                .PHONY: seed
                
                truncate:
                	docker exec -it $(container_id) php artisan eb:truncate
                .PHONY: truncate
                
                composer-install:
                	docker exec -it $(container_id) composer install
                .PHONY: composer-install
                
                composer-update:
                	docker exec -it $(container_id) composer update
                .PHONY: composer-update
                    
                

                npm:
                	docker exec -it $(container_id) npm install
                .PHONY: npm
                
                npm-dev:
                	docker exec -it $(container_id) npm run dev
                .PHONY: npm-dev
                
                jumpin:
                	docker exec -it $(container_id) bash
                .PHONY: jumpin
                
                test:
                	docker exec -it $(container_id) php -d pcov.enabled=1 vendor/bin/phpunit
                .PHONY: test
                
                larastan:
                	docker exec -it $(container_id) php vendor/bin/phpstan analyse
                .PHONY: larastan
                
                logs:
                	docker-compose logs -f
                .PHONY: logs
                
                queue:
                	docker exec -it $(container_id) php artisan queue:work
                .PHONY: queue
                
                run-ip:
                	docker inspect -f '{{ .NetworkSettings.Networks.gateway.IPAddress }}' `docker ps --filter "name=eb_app_run" --filter status=running --format "{{.ID}}"`
                .PHONY: run-ip
                    
                

            version: "3"
            services:
              traefik:
                image: traefik:v2.5
                command: --providers.docker # Enables the web UI and tells Traefik to listen to docker
                container_name: global_traefik
                restart: "always"
                ports:
                  - "80:80" # Port 80 is used for HTTP trafic
                  - "443:443" # Port 443 is used for HTTPS trafic
                volumes:
                  # Here is the mount of the traefik config
                  - ./traefik.toml:/etc/traefik/traefik.toml:ro
                  - ./tls-info.toml:/etc/traefik/tls-info.toml:ro
                  # Here is the mount of the local ~/ssl directory
                  - ./ssl:/etc/traefik/ssl:ro
                  # The docker socket is mounted for auto-discovery of new services
                  - /var/run/docker.sock:/var/run/docker.sock:ro
                networks:
                  # Attach the traefik container to the default network (which is the global "gateway" network)
                  - default
                labels:
                  - "traefik.enable=true"
                  - "traefik.http.routers.api.entrypoints=web-secure"
                  - "traefik.http.routers.api.rule=Host(`dashboard.local.j3j5.uy`)"
                  - "traefik.http.routers.api.service=api@internal"
                  - "traefik.http.routers.api.middlewares=auth"
                  - "traefik.http.routers.api.tls=true"
                  - "traefik.http.middlewares.auth.basicauth.users=admin:$$may1$$aBcN4ABC7$$RY/wKJaBc45aUbBcDubmre0"
            
            # Make the externally created network "gateway" available as network "default"
            networks:
              default:
                external: true
                name: gateway
                
            

docker-compose.yml


                version: '3'
                services:
                  # The Web Server
                  web:
                    image: nginx:1.24
                    volumes:
                      - ./:/var/www
                      - ./resources/docker/vhost.conf:/etc/nginx/conf.d/default.conf:ro
                    depends_on:
                      - app
                    restart: "always"
                    labels:
                      - "traefik.enable=true"
                      - "traefik.http.middlewares.redirect-2-https.redirectscheme.scheme=https"
                      - "traefik.http.routers.insecure.entrypoints=web"
                      - "traefik.http.routers.insecure.rule=Host(`project.dev.j3j5.uy`)"
                      - "traefik.http.routers.insecure.middlewares=redirect-2-https@docker"
                      - "traefik.http.routers.main.entrypoints=web-secure"
                      - "traefik.http.routers.main.rule=Host(`project.dev.j3j5.uy`)"
                      - "traefik.http.routers.main.tls=true"
                
                  networks:
                    default:
                      external: true
                      name: gateway
                    
                

Downsides

  • Every project needs to have docker-compose.yml and Makefile (not very DRY, specially the Makefile)
  • Ports 80 and 443 need to be free on your machine
  • Docker has to be installed and some free space is needed for the images
  • Let's Encrypt cert and priv key must be distributed to all devs
  • Different environments for git-hooks
  • Database access on non-linux platforms
A talk by Julio Foulquie

Upsides

  • One frontend for all projects (traefik)
  • Forget about ports within your projects (autowiring)
  • Real HTTPS (have your own valid certs)
  • Enable/disable environment with one command from scratch
    (make up and you're up and running)
  • All devs have the exact same environment
  • Ngrok compatible
    (you can still connect with 3rd parties or share your local project)
A talk by Julio Foulquie

QUESTIONS?

A talk by Julio Foulquie

👏 THANKS! 👏

A talk by Julio Foulquie

Use a spacebar or arrow keys to navigate.
Press 'P' to launch speaker console.