Laravel subdomains in Docker

Published on:

There are many ways you can add Docker to your Laravel application, but how do you add docker to your Laravel application when you need sub domain support? In this article I will walk through how I have done it in the past, this isn’t the only way to achieve this - but it is a way that I found works well for me.

It all starts with a docker-compose file, like most docker builds - I prefer to use docker-compose as it allows me to be pretty specific on behaviour and environment variables etc. Create a docker-compose.yaml in the root of your Laravel application. We are going to want to add 5 services to this: nginx, app, redis, mysql and traefik.

You do not need to keep redis if you do not wish to use it, however it is something I include in every laravel build as it provides great support for both Cache and Queue workers. Let’s walk through the compose file now:

Our Nginx Service

nginx:
    container_name: "${PROJECT_NAME}_nginx"
    build:
        context: ./docker/nginx
        dockerfile: Dockerfile
    depends_on:
        - app
    volumes:
        - ./:/var/www/vhost/crm:cached
        - ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf
        - ./docker/nginx/conf.d:/etc/nginx/conf.d
        - ./docker/nginx/ssl:/etc/nginx/ssl
    working_dir: /var/www/vhost/
    ports:
        - '443:443'
        - '9008:9008'
    networks:
        - crm
    labels:
        - 'traefik.http.routers.${PROJECT_NAME}_nginx.rule=HostRegexp(`${APP_DOMAIN}`, `{subdomain:[a-z]+.${APP_DOMAIN}}`)'
        - 'traefik.http.routers.${PROJECT_NAME}_nginx.priority=1'
        - 'traefik.docker.network=proxy'

In my .env file I add a new variable called PROJECT_NAME which could be different to my app name, something that is more system friendly and identifiable. Your docker-compose file can pick up this environment variable, and use it as part of it’s build process. We want the name of our nginx container to be specific for our project, so we use the env variable and add _nginx on the end. We then want to specify the build portion of our service, we want to pull in a specific Dockerfile for nginx to allow us to configure the container in our own way. This container will depend on the app service being available, otherwise we will not be able to serve anything useful. We also want to mount a few volumes:

  • Our application itself, which we will cache between builds
  • Our nginx configuration
  • Our SSL certificates.

We then set our working directory, ports, and which network we want to run on. Finally we get to the labels part of our service, we want nginx to handle the routing - but we will have a layer around that which handles the trafic ingress and sends it to the container it is needed at. The important label is the first one:

traefik.http.routers.${PROJECT_NAME}_nginx.rule=HostRegexp(`${APP_DOMAIN}`, `{subdomain:[a-z]+.${APP_DOMAIN}}`)

what this does is tell our trefik service that we want a router for this project and the nginx rule should match a specific regular expression. As you can see we have another .env variable called APP_DOMAIN which is what we want our container to respond as, I usually set this as project-name.localhost - it is important to use .localhost so that traefik will work correctly here. But all the regex does is match any subdomain with the app domain part and route it to nginx to pass to your Laravel application. Our docker network is set to proxy, so that requests proxy through traefik to our nginx service, and we want the priority to be 1.

Let’s also have a look at the other parts we need aside from the docker-compose definition.

The nginx Dockerfile should be created under: docker/nginx/Dockerfile and contain the following:

# Offical Docker Image for Nginx
# https://hub.docker.com/_/nginx
FROM nginx:alpine

# Set Current Directory
WORKDIR /var/www/vhost/

The nginx configuration should be created under: docker/nginx/nginx.conf and contain the following:

pid /run/nginx.pid;
worker_processes auto;
worker_rlimit_nofile 65535;

events {
    multi_accept on;
    worker_connections 65535;
}

http {
    charset utf-8;
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    server_tokens off;
    log_not_found off;
    types_hash_max_size 2048;
    client_max_body_size 16M;

    # MIME
    include mime.types;
    default_type application/octet-stream;

    # logging
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log warn;

    # SSL
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:50m;
    ssl_session_tickets off;

    # Diffie-Hellman parameter for DHE ciphersuites
    #ssl_dhparam /etc/nginx/dhparam.pem;

    # OWASP B (Broad Compatibility) configuration
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256;
    ssl_prefer_server_ciphers on;

    # OCSP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 1.1.1.1 1.0.0.1 8.8.8.8 8.8.4.4 208.67.222.222 208.67.220.220 valid=60s;
    resolver_timeout 2s;

    # load configs
    include /etc/nginx/conf.d/*.conf;
}

Our sites configuration should be be: docker/nginx/conf.g/site.conf and contain the following:

server {

   #listen 80;
   listen [::]:80;

   # For https
   listen 443 ssl;
   listen [::]:443 ssl ipv6only=on;
   ssl_certificate /etc/nginx/ssl/app-cert.pem;
   ssl_certificate_key /etc/nginx/ssl/app-key.pem;

   root /var/www/vhost/crm/public;
   index index.php index.html index.htm;

   location / {
        try_files $uri $uri/ /index.php$is_args$args;
   }

   location ~ \.php$ {
       try_files $uri /index.php =404;
       # We are using our app service container name instead of 127.0.0.1 as our connection
       fastcgi_pass app:9000;
       fastcgi_index index.php;
       fastcgi_buffers 16 16k;
       fastcgi_buffer_size 32k;
       fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
       #fixes timeouts
       fastcgi_read_timeout 600;
       include fastcgi_params;
   }

   location ~ /\.ht {
       deny all;
   }

   location /.well-known/acme-challenge/ {
       root /var/www/letsencrypt/;
       log_not_found off;
   }

   error_log /var/log/nginx/laravel_error.log;
   access_log /var/log/nginx/laravel_access.log;
}

Finally we will need to have our SSL configuration created under: docker/nginx/ssl/openssl.cnf and contain the following:

[req]
default_bits           = 2048
default_md             = sha256
encrypt_key            = no
prompt                 = no
distinguished_name     = subject
req_extensions         = req_ext
x509_extensions        = x509_ext

[ subject ]
C                      = Country
ST                     = State
L                      = Location
O                      = Organisation
OU                     = Team
emailAddress           = [email protected]
CN                     = localhost

[ req_ext ]
subjectKeyIdentifier   = hash
basicConstraints       = CA:FALSE
keyUsage               = digitalSignature, keyEncipherment
extendedKeyUsage       = serverAuth, clientAuth
subjectAltName         = @alternate_names
nsComment              = "OpenSSL Generated Certificate"

[ x509_ext ]
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid,issuer
basicConstraints       = CA:FALSE
keyUsage               = digitalSignature, keyEncipherment
extendedKeyUsage       = serverAuth, clientAuth
subjectAltName         = @alternate_names
nsComment              = "OpenSSL Generated Certificate"

[ alternate_names ]
DNS.1                  = localhost
IP.1                   = 127.0.0.1

Finally we want to be able to create SSL certificates for our application, so run the following command inside docker/nginx/ssl:

mkcert "*.crm.localhost" "crm.localhost"

Make sure you swap crm.localhost above to whatever your app domain is set to!

We then want to rename _wildcard.crm.localhost+1.pem to app-cert.pem, and _wildcard.crm.localhost_1-key.pem to app-key.pem so that our application responds over SSL. Again your file names may differ.

Our App Service

Our App Service is a little more typical and has no requirements for it on traefik as nginx handles loading this:

app:
    container_name: "${PROJECT_NAME}_php"
    build:
        context: ./docker/php
        dockerfile: Dockerfile
    environment:
        PHP_MEMORY_LIMIT: '512M'
        COMPOSER_MEMORY_LIMIT: '-1'
    user: 501:501
    volumes:
        - ./:/var/www/vhost/crm:cached
    working_dir: /var/www/vhost/crm
    ports:
        - '9003:9003'
    networks:
        - crm

Again, like our nginx service, we use the Project Name env variable to set the containers name, ensuring it is easy to spot and manage. We then load in a Dockerfile so we can be specific on our build, and set some environment variables. We can control the PHP memory limit here easily, and we set the composer memory limit to -1 - which as of composer 2.* is not as important. This is something I used to use when composer would run out of memory sometimes in fetching dependencies in large projects, I keep this in still more as a safety net than anything else. We set our user to our current user, on a mac if you open your terminal and run id you will get an output - find the ID that correlates to your user account and replace this in the service.

We have a few additional configuration files to add to our app service, so inside docker/php we will need to create a Dockerfile and add the following to it:

# Offical Docker Image for PHP
# https://hub.docker.com/_/php
FROM php:8.1-fpm

# Set Current Directory
WORKDIR /var/www/vhost/

# Install dependencies
RUN apt-get clean && apt-get update && apt-get upgrade -y && apt-get install -y \
    git \
    libcurl4-openssl-dev \
    libonig-dev \
    libpng-dev \
    libssl-dev \
    libicu-dev \
    libxml2-dev \
    libzip-dev \
    unzip \
    wget \
    zip \
    tzdata

RUN docker-php-ext-configure intl
# PHP Extensions
RUN docker-php-ext-install \
    bcmath \
    exif \
    gd \
    mysqli \
    opcache \
    pdo_mysql \
    pcntl \
    xml \
    zip \
    intl

# Install Composer from Official Docker Image
# https://hub.docker.com/_/composer
COPY --from=composer:2.2 /usr/bin/composer /usr/bin/composer

This dockerfile took me awhile to figure out when I first created it, as PHP 8.1 comes with some extensions pre-built now so cannot find certain extensions to install as they now belong in core. If you are converting your own Dockerfile for PHP here from pre 8.1 please bear that in mind.

Then we have an opcache and redis ini file within docker/php/config:

[opcache]
opcache.enable=1
; 0 means it will check on every request
; 0 is irrelevant if opcache.validate_timestamps=0 which is desirable in production
opcache.revalidate_freq=0
opcache.validate_timestamps=1
opcache.max_accelerated_files=10000
opcache.memory_consumption=192
opcache.max_wasted_percentage=10
opcache.interned_strings_buffer=16
opcache.fast_shutdown=1
[redis]

You can configure the PHP extensions how works best for you within these now.

The Redis Service

Much like other services, we use the project name env variable to prefix the container name, followed by the name of the service. We then want to set the ports volumes and network:

redis:
    image: redis:latest
    container_name: "${PROJECT_NAME}_redis"
    ports:
      - '6379:6379'
    volumes:
      - 'crm_redis:/data'
    networks:
      - crm

As you can see, this is a super simple docker service with very little customisation needed.

The MySQL Service

Again, we prefix the MySQL service with the project name env variable, and then we set some environment variables for the container so that we can create the database with credentials etc:

mysql:
    image: mariadb:latest
    container_name: "${PROJECT_NAME}_mysql"
    environment:
      MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}'
      MYSQL_DATABASE: '${DB_DATABASE}'
      MYSQL_USER: '${DB_USERNAME}'
      MYSQL_PASSWORD: '${DB_PASSWORD}'
      MYSQL_ROOT_HOST: '%'
      MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
      command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    restart: always
    volumes:
      - 'crm_mysql:/data'
    ports:
      - '${FORWARD_DB_PORT:-4406}:3306'
    networks:
      - crm

These env variables will be pulled from your .env file, so make sure you adjust these as required before running any docker commands.

Our Traefik Service

This is one of our most important services for our sub-domain set up, traefik basically acts as an ingress service for all traffic coming in on docker setup, and then proxies the request to one of its “routers” which we defined on our nginx service as a label. It is not an overly complex set up, but can be hard to get right (which is why I am writing this article).

traefik:
    image: traefik:v2.0
    container_name: "${PROJECT_NAME}_traefik"
    restart: always
    command:
      - --entrypoints.web.address=:80
      - --providers.docker=true
      - --api.insecure=true
      - --log.level=debug
    volumes:
      - '/var/run/docker.sock:/var/run/docker.sock'
    ports:
      - '80:80'
      - '8080:8080'
    networks:
      - crm

We have a few options for our command, where we want to specify the web address to see entrypoints to port 80 (we aren’t using port 80 for nginx so this is fine). We then want to set the provider to be docker and the api to be insecure. I am not 100% sure on what all of these commands do, as I do not pretend to be an expert with docker. Finally we want to mount the docker socket we have to the one in the container to allow it to all connect and allow us to smile. This may be different for yourself, so if you have issues make sure you check this volume if you do encounter issues.

The full docker compose setup

version: '3'

services:
  nginx:
    container_name: "${PROJECT_NAME}_nginx"
    build:
      context: ./docker/nginx
      dockerfile: Dockerfile
    depends_on:
      - app
    volumes:
      - ./:/var/www/vhost/crm:cached
      - ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./docker/nginx/conf.d:/etc/nginx/conf.d
      - ./docker/nginx/ssl:/etc/nginx/ssl
    working_dir: /var/www/vhost/
    ports:
      - '443:443'
      - '9008:9008'
    networks:
      - crm
    labels:
      - 'traefik.http.routers.${PROJECT_NAME}_nginx.rule=HostRegexp(`${APP_DOMAIN}`, `{subdomain:[a-z]+.${APP_DOMAIN}}`)'
      - 'traefik.http.routers.${PROJECT_NAME}_nginx.priority=1'
      - 'traefik.docker.network=proxy'

  app:
    container_name: "${PROJECT_NAME}_php"
    build:
      context: ./docker/php
      dockerfile: Dockerfile
    environment:
      PHP_MEMORY_LIMIT: '512M'
      COMPOSER_MEMORY_LIMIT: '-1'
    user: 501:501
    volumes:
      - ./:/var/www/vhost/crm:cached
    working_dir: /var/www/vhost/crm
    ports:
      - '9003:9003'
    networks:
      - crm

  redis:
    image: redis:latest
    container_name: "${PROJECT_NAME}_redis"
    ports:
      - '6379:6379'
    volumes:
      - 'crm_redis:/data'
    networks:
      - crm

  mysql:
    image: mariadb:latest
    container_name: "${PROJECT_NAME}_mysql"
    environment:
      MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}'
      MYSQL_DATABASE: '${DB_DATABASE}'
      MYSQL_USER: '${DB_USERNAME}'
      MYSQL_PASSWORD: '${DB_PASSWORD}'
      MYSQL_ROOT_HOST: '%'
      MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
      command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    restart: always
    volumes:
      - 'crm_mysql:/data'
    ports:
      - '${FORWARD_DB_PORT:-4406}:3306'
    networks:
      - crm

  traefik:
    image: traefik:v2.0
    container_name: "${PROJECT_NAME}_traefik"
    restart: always
    command:
      - --entrypoints.web.address=:80
      - --providers.docker=true
      - --api.insecure=true
      - --log.level=debug
    volumes:
      - '/var/run/docker.sock:/var/run/docker.sock'
    ports:
      - '80:80'
      - '8080:8080'
    networks:
      - crm

networks:
  crm:
    driver: bridge

volumes:
  crm_mysql:
    driver: local

  crm_redis:
    driver: local

Finally what I like to do with every docker project, is create a makefile - it allows me to have easier and more convienient commands to access what I need when I need it, you do not need to do this yourself but I will include the file here:

.RECIPEPREFIX +=
.DEFAULT_GOAL := help
PROJECT_NAME=jump
include .env

help:
	@echo "Welcome to $(PROJECT_NAME) IT Support, have you tried turning it off and on again?"

install:
	@composer install

test:
	@docker exec $(PROJECT_NAME)_php ./vendor/bin/pest --parallel

coverage:
	@docker exec $(PROJECT_NAME)_php ./vendor/bin/pest --coverage

migrate:
	@docker exec $(PROJECT_NAME)_php php artisan migrate

seed:
	@docker exec $(PROJECT_NAME)_php php artisan db:seed

fresh:
	@docker exec crm_php php artisan migrate:fresh

analyse:
	./vendor/bin/phpstan analyse --memory-limit=256m

generate:
	@docker exec $(PROJECT_NAME)_php php artisan ide-helper:models --write

nginx:
	@docker exec -it $(PROJECT_NAME)_nginx /bin/sh

php:
	@docker exec -it $(PROJECT_NAME)_php /bin/sh

mysql:
	@docker exec -it $(PROJECT_NAME)_mysql /bin/sh

redis:
	@docker exec -it $(PROJECT_NAME)_redis /bin/sh

This just contains some command that I find useful while working with docker - your mileage may vary!

If you have any questions, or think this could be simplified in anyway please feel free to reach out on twitter and let me know your thoughts.

If you want to see me walking through this set up, I have a video on youtube where I add this to a project myself:

What people say about me

Aaron Francis

Aaron Francis

Founder @ Try Hard Studies

Steve is one of the most thorough and thoughtful educators I've seen. He puts in a ton of effort and it clearly shows.
Alex Six

Alex Six

Head of Developer Relations @ Filament PHP

Steve is the teacher you wish you had in school. He’s informative, entertaining, and passionate about what he does. I’m always excited to see more of Steve’s content!
Brent Roose

Brent Roose

Developer Advocate @ jetBrains

To me, there's one word that comes to mind: 'passion'. There are only a handful of people with the same passion for programming as Steve.
Chris Miller

Chris Miller

Lead Developer @ MMS Marketing

Steve is a great developer with a wealth of knowledge that has contributed to many projects we have. His passion for API means we benefit from his aim for the best
Davor Minchorov

Davor Minchorov

Senior PHP Engineer @ Adeva

Steve is an awesome guy who knows a thing or two about APIs. I’ve learned so much from his content on his YouTube channel as well as his API video course.
Eric Barnes

Eric Barnes

Founder @ Laravel News

Steve is an awesome writer and communicator, he can take complex problems and communicate them in a way that anyone can understand.
Gary Clarke

Gary Clarke

YouTuber @ Gary Clarke Tech

Steve is highly engaging and an expert in what he does. I bookmark everything he publishes and refer back to it on a regular basis. The API king!
Aaron Francis

Aaron Francis

Founder @ Try Hard Studies

Steve is one of the most thorough and thoughtful educators I've seen. He puts in a ton of effort and it clearly shows.
Alex Six

Alex Six

Head of Developer Relations @ Filament PHP

Steve is the teacher you wish you had in school. He’s informative, entertaining, and passionate about what he does. I’m always excited to see more of Steve’s content!
Brent Roose

Brent Roose

Developer Advocate @ jetBrains

To me, there's one word that comes to mind: 'passion'. There are only a handful of people with the same passion for programming as Steve.
Chris Miller

Chris Miller

Lead Developer @ MMS Marketing

Steve is a great developer with a wealth of knowledge that has contributed to many projects we have. His passion for API means we benefit from his aim for the best
Davor Minchorov

Davor Minchorov

Senior PHP Engineer @ Adeva

Steve is an awesome guy who knows a thing or two about APIs. I’ve learned so much from his content on his YouTube channel as well as his API video course.
Eric Barnes

Eric Barnes

Founder @ Laravel News

Steve is an awesome writer and communicator, he can take complex problems and communicate them in a way that anyone can understand.
Gary Clarke

Gary Clarke

YouTuber @ Gary Clarke Tech

Steve is highly engaging and an expert in what he does. I bookmark everything he publishes and refer back to it on a regular basis. The API king!
Aaron Francis

Aaron Francis

Founder @ Try Hard Studies

Steve is one of the most thorough and thoughtful educators I've seen. He puts in a ton of effort and it clearly shows.
Alex Six

Alex Six

Head of Developer Relations @ Filament PHP

Steve is the teacher you wish you had in school. He’s informative, entertaining, and passionate about what he does. I’m always excited to see more of Steve’s content!
Brent Roose

Brent Roose

Developer Advocate @ jetBrains

To me, there's one word that comes to mind: 'passion'. There are only a handful of people with the same passion for programming as Steve.
Chris Miller

Chris Miller

Lead Developer @ MMS Marketing

Steve is a great developer with a wealth of knowledge that has contributed to many projects we have. His passion for API means we benefit from his aim for the best
Davor Minchorov

Davor Minchorov

Senior PHP Engineer @ Adeva

Steve is an awesome guy who knows a thing or two about APIs. I’ve learned so much from his content on his YouTube channel as well as his API video course.
Eric Barnes

Eric Barnes

Founder @ Laravel News

Steve is an awesome writer and communicator, he can take complex problems and communicate them in a way that anyone can understand.
Gary Clarke

Gary Clarke

YouTuber @ Gary Clarke Tech

Steve is highly engaging and an expert in what he does. I bookmark everything he publishes and refer back to it on a regular basis. The API king!
Aaron Francis

Aaron Francis

Founder @ Try Hard Studies

Steve is one of the most thorough and thoughtful educators I've seen. He puts in a ton of effort and it clearly shows.
Alex Six

Alex Six

Head of Developer Relations @ Filament PHP

Steve is the teacher you wish you had in school. He’s informative, entertaining, and passionate about what he does. I’m always excited to see more of Steve’s content!
Brent Roose

Brent Roose

Developer Advocate @ jetBrains

To me, there's one word that comes to mind: 'passion'. There are only a handful of people with the same passion for programming as Steve.
Chris Miller

Chris Miller

Lead Developer @ MMS Marketing

Steve is a great developer with a wealth of knowledge that has contributed to many projects we have. His passion for API means we benefit from his aim for the best
Davor Minchorov

Davor Minchorov

Senior PHP Engineer @ Adeva

Steve is an awesome guy who knows a thing or two about APIs. I’ve learned so much from his content on his YouTube channel as well as his API video course.
Eric Barnes

Eric Barnes

Founder @ Laravel News

Steve is an awesome writer and communicator, he can take complex problems and communicate them in a way that anyone can understand.
Gary Clarke

Gary Clarke

YouTuber @ Gary Clarke Tech

Steve is highly engaging and an expert in what he does. I bookmark everything he publishes and refer back to it on a regular basis. The API king!
Jack Ellis

Jack Ellis

Co-Founder @ Fathom Analytics

Steve is one of the top API experts in the world and has been teaching our community for years.
Laravel Jutsu

Laravel Jutsu

YouTuber @ Laravel Jutsu

Steve has a profound mastery of APIs and a public-serving teaching approach.
Luke Downing

Luke Downing

Educator @ Laracasts

Steve is a stream of knowledge. If you like clean, strict code and silky APIs, there’s nobody I’d sooner turn to for advice!
Sam Carré

Sam Carré

Head of Engineering @ Plannr CRM

Steve’s enthusiasm and positive attitude makes his educational content super easy to digest and makes learning new tools and languages really fun.
Peter Thomson

Peter Thomson

Chief Technology Officer @ Ice House Ventures

Steve is an absolute weapon. We came for the APIs but we stayed for the clean code, robust systems architecture and deep knowledge of Laravel conventions. Steve made our code faster, more stable and easier to maintain ourselves in the future.
Stephen Rees-Carter

Stephen Rees-Carter

Friendly Hacker @ Valorin Security

Steve is one of my favourite people. He has a huge passion for teaching developers about APIs and good coding practices.
Steven Tey

Steven Tey

Founder @ Dub.co

Steve brings knowledge and passion to the community, with easy to follow content that adds real value to any company that uses him.
Jack Ellis

Jack Ellis

Co-Founder @ Fathom Analytics

Steve is one of the top API experts in the world and has been teaching our community for years.
Laravel Jutsu

Laravel Jutsu

YouTuber @ Laravel Jutsu

Steve has a profound mastery of APIs and a public-serving teaching approach.
Luke Downing

Luke Downing

Educator @ Laracasts

Steve is a stream of knowledge. If you like clean, strict code and silky APIs, there’s nobody I’d sooner turn to for advice!
Sam Carré

Sam Carré

Head of Engineering @ Plannr CRM

Steve’s enthusiasm and positive attitude makes his educational content super easy to digest and makes learning new tools and languages really fun.
Peter Thomson

Peter Thomson

Chief Technology Officer @ Ice House Ventures

Steve is an absolute weapon. We came for the APIs but we stayed for the clean code, robust systems architecture and deep knowledge of Laravel conventions. Steve made our code faster, more stable and easier to maintain ourselves in the future.
Stephen Rees-Carter

Stephen Rees-Carter

Friendly Hacker @ Valorin Security

Steve is one of my favourite people. He has a huge passion for teaching developers about APIs and good coding practices.
Steven Tey

Steven Tey

Founder @ Dub.co

Steve brings knowledge and passion to the community, with easy to follow content that adds real value to any company that uses him.
Jack Ellis

Jack Ellis

Co-Founder @ Fathom Analytics

Steve is one of the top API experts in the world and has been teaching our community for years.
Laravel Jutsu

Laravel Jutsu

YouTuber @ Laravel Jutsu

Steve has a profound mastery of APIs and a public-serving teaching approach.
Luke Downing

Luke Downing

Educator @ Laracasts

Steve is a stream of knowledge. If you like clean, strict code and silky APIs, there’s nobody I’d sooner turn to for advice!
Sam Carré

Sam Carré

Head of Engineering @ Plannr CRM

Steve’s enthusiasm and positive attitude makes his educational content super easy to digest and makes learning new tools and languages really fun.
Peter Thomson

Peter Thomson

Chief Technology Officer @ Ice House Ventures

Steve is an absolute weapon. We came for the APIs but we stayed for the clean code, robust systems architecture and deep knowledge of Laravel conventions. Steve made our code faster, more stable and easier to maintain ourselves in the future.
Stephen Rees-Carter

Stephen Rees-Carter

Friendly Hacker @ Valorin Security

Steve is one of my favourite people. He has a huge passion for teaching developers about APIs and good coding practices.
Steven Tey

Steven Tey

Founder @ Dub.co

Steve brings knowledge and passion to the community, with easy to follow content that adds real value to any company that uses him.
Jack Ellis

Jack Ellis

Co-Founder @ Fathom Analytics

Steve is one of the top API experts in the world and has been teaching our community for years.
Laravel Jutsu

Laravel Jutsu

YouTuber @ Laravel Jutsu

Steve has a profound mastery of APIs and a public-serving teaching approach.
Luke Downing

Luke Downing

Educator @ Laracasts

Steve is a stream of knowledge. If you like clean, strict code and silky APIs, there’s nobody I’d sooner turn to for advice!
Sam Carré

Sam Carré

Head of Engineering @ Plannr CRM

Steve’s enthusiasm and positive attitude makes his educational content super easy to digest and makes learning new tools and languages really fun.
Peter Thomson

Peter Thomson

Chief Technology Officer @ Ice House Ventures

Steve is an absolute weapon. We came for the APIs but we stayed for the clean code, robust systems architecture and deep knowledge of Laravel conventions. Steve made our code faster, more stable and easier to maintain ourselves in the future.
Stephen Rees-Carter

Stephen Rees-Carter

Friendly Hacker @ Valorin Security

Steve is one of my favourite people. He has a huge passion for teaching developers about APIs and good coding practices.
Steven Tey

Steven Tey

Founder @ Dub.co

Steve brings knowledge and passion to the community, with easy to follow content that adds real value to any company that uses him.

© 2025 JustSteveKing. All rights reserved.