DEV Community

Cover image for How to Enable SSL for Local Laravel Sail Development using Caddy and Docker
Adrian Mejias
Adrian Mejias

Posted on

How to Enable SSL for Local Laravel Sail Development using Caddy and Docker

Let's talk SSL and local development; don't worry, I've been searching too.

There is an issue with developing locally, not just with Laravel, where developers who are building saas products aren't able to get a clean setup for SSL in the browser. This makes setting things up like PWAs or Google sign-in buttons impossible if the certificate isn't valid.

Given the power of Docker and Caddy, the dream is real. You can absolutely use this for non-Laravel Sail web applications as well.

The end result of implementing the code below should give you a couple of certificates (intermediate.crt, laravel.test.crt) that you can install to your local system.

GitHub Gist: https://gist.github.com/adrianmejias/0997f2b8a20715428f594a4798e034f5

Directory Structure

  • docker/
    • caddy/
    • authorities/ (intermediate.crt)
    • certificates/
      • laravel.test/ (laravel.test.crt)
    • Caddyfile
    • Dockerfile
    • start-container
  • docker-compose.yml

Files

docker-compose.yml

# ...
  laravel.test:
        # Comment or remove ports
        # ports:
        #     - "${APP_PORT:-80}:80"
   # ...
   caddy:
        build:
            context: "./docker/caddy"
            dockerfile: Dockerfile
            args:
                WWWGROUP: "${WWWGROUP}"
        restart: unless-stopped
        ports:
            - "${APP_PORT:-80}:80"
            - "${APP_SSL_PORT:-443}:443"
        environment:
            LARAVEL_SAIL: 1
            HOST_DOMAIN: laravel.test
        volumes:
            - "./docker/caddy/Caddyfile:/etc/caddy/Caddyfile"
            - ".:/srv:cache"
            - "./docker/caddy/certificates:/data/caddy/certificates/local"
            - "./docker/caddy/authorities:/data/caddy/pki/authorities/local"
            - "sailcaddy:/data:cache"
            - "sailcaddyconfig:/config:cache"
        networks:
            - sail
        depends_on:
            - laravel.test
# ...
volumes:
    # ...
    sailcaddy:
        external: true
    sailcaddyconfig:
        driver: local
Enter fullscreen mode Exit fullscreen mode

docker/caddy/Dockerfile

FROM caddy:alpine

LABEL maintainer="Adrian Mejias"

ARG WWWGROUP

ENV DEBIAN_FRONTEND noninteractive
ENV TZ=UTC

RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

RUN apk add --no-cache bash \
    && apk add --no-cache nss-tools \
    && rm -rf /var/cache/apk/*

RUN addgroup -S $WWWGROUP
RUN adduser -G $WWWGROUP -u 1337 -S sail

COPY start-container /usr/local/bin/start-container
RUN chmod +x /usr/local/bin/start-container

ENTRYPOINT ["start-container"]
Enter fullscreen mode Exit fullscreen mode

docker/caddy/start-container

#!/usr/bin/env sh

if [ ! -z "$WWWUSER" ]; then
    addgroup $WWWUSER sail
fi

if [ $# -gt 0 ]; then
    # @todo find alpine equivilent of below
    # exec gosu $WWWUSER "$@"
else
    /usr/bin/caddy run --config /etc/caddy/Caddyfile --adapter caddyfile
fi
Enter fullscreen mode Exit fullscreen mode

docker/caddy/Caddyfile

{
    admin off
    # debug

    on_demand_tls {
        ask http://laravel.test/caddy
    }

    local_certs
}

:80 {
    reverse_proxy laravel.test {
        header_up Host {host}
        header_up X-Real-IP {remote}
        header_up X-Forwarded-Host {host}
        header_up X-Forwarded-For {remote}
        header_up X-Forwarded-Port 443
        # header_up X-Forwarded-Proto {scheme}

        health_timeout 5s
    }
}

:443 {
    tls internal {
        on_demand
    }

    reverse_proxy laravel.test {
        header_up Host {host}
        header_up X-Real-IP {remote}
        header_up X-Forwarded-Host {host}
        header_up X-Forwarded-For {remote}
        header_up X-Forwarded-Port 443
        # header_up X-Forwarded-Proto {scheme}

        health_timeout 5s
    }
}
Enter fullscreen mode Exit fullscreen mode

app/Http/Controllers/CaddyController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class CaddyController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function __invoke(Request $request)
    {
        if (in_array($request->query('domain'), config('caddy.authorized'))) {
            return response('Domain Authorized');
        }

        abort(503);
    }
}
Enter fullscreen mode Exit fullscreen mode

config/caddy.php

<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Authorized Domains
    |--------------------------------------------------------------------------
    |
    | Domains that are authorized to be viewed through Caddy.
    |
    */

    'authorized' => [
        'laravel.test',
        // 'app.laravel.test',
    ],

];
Enter fullscreen mode Exit fullscreen mode

app/Http/Middleware/TrustProxies.php

<?php

namespace App\Http\Middleware;

use Illuminate\Http\Middleware\TrustProxies as Middleware;
use Illuminate\Http\Request;

class TrustProxies extends Middleware
{
    /**
     * The trusted proxies for this application.
     *
     * @var array|string|null
     */
    protected $proxies = '*'; // Add wildcard or specific domain(s)

    /**
     * The headers that should be used to detect proxies.
     *
     * @var int
     */
    protected $headers =
        Request::HEADER_X_FORWARDED_FOR |
        Request::HEADER_X_FORWARDED_HOST |
        Request::HEADER_X_FORWARDED_PORT |
        Request::HEADER_X_FORWARDED_PROTO |
        Request::HEADER_X_FORWARDED_AWS_ELB;
}
Enter fullscreen mode Exit fullscreen mode

routes/web.php

<?php

use App\Http\Controllers\CaddyController;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/caddy', CaddyController::class)->name('caddy');

// ...
Enter fullscreen mode Exit fullscreen mode

Discussion (6)

Collapse
mrdionjr profile image
Salomon Dion

Great post ! I've followed these steps but the certificate is not generated, only the authorities folder is populated. Can you please, look into it ? Also the start-container file produce and error when the if block is left empty, I've added an echo there.

Collapse
stradivario profile image
Kristiqn Tachev • Edited on

Why do we need a SSL for local development ?
I don't get it to be honest.

There is an issue with developing locally, not just with Laravel, where developers who are building saas products aren't able to get a clean setup for SSL in the browser. This makes setting things up like PWAs or Google sign-in buttons impossible if the certificate isn't valid.
Enter fullscreen mode Exit fullscreen mode

This is not true what problem do you have with PWA and Google sign in button ?

Collapse
warix3 profile image
Warix3

I need it to test paypal webhooks, they don't work if the endpoint is not https, even in the sandbox version

Collapse
adrianmejias profile image
Adrian Mejias Author

A page can't qualify as a Progressive Web App (PWA) if it doesn't run on HTTPS; many core PWA technologies, such as service workers, require HTTPS.

Are you trolling?

Collapse
stradivario profile image
Kristiqn Tachev

Hi Man!

Sorry that it sounds like that but no i am not.

Have you try adding secure origin to chrome flags?

chrome://flags/#unsafely-treat-insecure-origin-as-secure

or maybe running firefox

devtools.serviceWorkers.testing.enabled

There's an exception to the HTTPS requirement in place to facilitate local development: if you access your page and service worker script via localhost[:port], or via 127.x.y.z[:port], then service workers should be enabled without any further actions

Does this sound like a trolling ? :)

You made it possible by hard way which is for running it on the server certified but there is a way to do it without introducing these flows.

Cheers

Thread Thread
adrianmejias profile image
Adrian Mejias Author • Edited on

I was absolutely not asking for your help. dev.to/aspittel/comment/7d5a

Just push your comment out and let the community read upon it.

Also, if anyone isn't signed up for Hacktoberfest, you can do so here: hacktoberfest.digitalocean.com/