Add Other Services to Your Project

Since Spin is based off of Docker Compose and Docker Swarm Mode, the possibilities are endless when it comes to expanding your infrastructure with Spin.

Important Notes

Every infrastructure configuration is unique and you should always prioritize security when applying configurations. Be sure to encrypt secrets where possible, but for demonstration purposes we will show you examples in plain text.

We make a few assumptions in our examples below:

  • Your development network is called development and properly created in your Docker Compose file
  • You have the proper configurations made available in your .infrastructure folder with the .gitignore properly configured

MariaDB

Example: docker-compose.yml

services:
  mariadb: 
    image: mariadb:11.4

Example: docker-compose.dev.yml

services:
  mariadb:
    networks:
      - development
    volumes:
      - ./.infrastructure/volume_data/mariadb/database_data/:/var/lib/mysql
    environment:
        MYSQL_ROOT_PASSWORD: "rootpassword"
        MYSQL_DATABASE: "laravel"
        MYSQL_USER: "mysqluser"
        MYSQL_PASSWORD: "mysqlpassword"
    ports:
      - "3306:3306"

networks:
  development:

MeiliSearch

Example: docker-compose.yml

services:
  meilisearch:
    image: getmeili/meilisearch:v1.9
    environment:
      MEILI_NO_ANALYTICS: "true"

Example: docker-compose.dev.yml

services:
  meilisearch:
    environment: 
      MEILI_MASTER_KEY: "masterKey"
    volumes: 
      - ./.infrastructure/volume_data/meilisearch/meilisearch_data:/meili_data:cached
    networks:
      - development

networks:
  development:

MySQL

Example: docker-compose.yml

services:
  mysql: 
    image: mysql:8.4

Example: docker-compose.dev.yml

services:
  mysql:
    networks:
      - development
    volumes:
      - ./.infrastructure/volume_data/mysql/database_data/:/var/lib/mysql
    environment:
        MYSQL_ROOT_PASSWORD: "rootpassword"
        MYSQL_DATABASE: "laravel"
        MYSQL_USER: "mysqluser"
        MYSQL_PASSWORD: "mysqlpassword"
    ports:
      - "3306:3306"

networks:
  development:

Node

Example: docker-compose.yml

services:
  node:
    image: node:20
    working_dir: /usr/src/app

Example: docker-compose.dev.yml

services:
  node:
    command: "yarn dev"
    volumes:
      - .:/usr/src/app:cached
    networks: 
      - development
    ports:
      - 3000:3000 # Remove the ports if you are using something like Trafeik or Caddy (recommended)

networks:
  development:

Commands

You can see the "command:" calls to run yarn dev. This may need to be changed, depending on your project (and if you are using npm or yarn). Feel free to remove this command if you prefer to run the commands manually.

Using with Traefik

The example above directly exposes the Node server on port 3000. You'll likely never do that in production. If you use something like Traefik, you would replace the ports with labels, like this:

Node Traefik Labels Example

    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.mynodeapp.rule=Host(`mynodeapp.dev.test`)"
      - "traefik.http.routers.mynodeapp.entrypoints=websecure"
      - "traefik.http.routers.mynodeapp.tls=true"
      - "traefik.http.services.mynodeapp.loadbalancer.server.port=3000"
      - "traefik.http.services.mynodeapp.loadbalancer.server.scheme=http"

The label adds certain metadata to your container, telling Traefik to route "mynodeapp.dev.test" to port 3000 on your node container. All web traffic will enter through Traefik first, then to your Node container -- which is a more realistic scenario to what you can run in production.

PHP

We use our open source PHP Docker Images, which are based on the official PHP images, but production-ready by default and optimized for Laravel out of the box. Read our guide on selecting the right image variation for your project.

Example: docker-compose.yml

services:
  php:
    build:
      context: . # Look for Dockerfile in the project root
      target: base

Example: docker-compose.dev.yml

services:
  php:
    build:
      # Notice how our build calls the "development" target.
      # See the Dockerfile for more details
      target: development
      args:
        # Spin loads the UID and GID to match the host in development
        # This is how we fix permission errors in development
        USER_ID: ${SPIN_USER_ID}
        GROUP_ID: ${SPIN_GROUP_ID}
    volumes:
      - .:/var/www/html/
    ports:
      - 80:80 # Remove the "ports" section if using Traefik or Caddy (recommended)
    networks:
      - development

networks:
  development:

Example: Dockerfile

# Learn more about the Server Side Up PHP Docker Images at:
# https://serversideup.net/open-source/docker-php/

FROM serversideup/php:8.3-fpm-nginx AS base

FROM base AS development

# Fix permission issues in development by setting the "www-data"
# user to the same user and group that is running docker.
ARG USER_ID
ARG GROUP_ID
RUN docker-php-serversideup-set-id www-data ${USER_ID} ${GROUP_ID}

FROM base AS deploy
COPY --chown=www-data:www-data . /var/www/html

Using with Traefik

The docker-compose.yml examples above exposes the HTTP server on port 80. If you use something like Traefik, you would replace the ports with labels, like this:

Laravel Traefik Labels Example

    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.laravel.rule=HostRegexp(`laravel.dev.test`)"
      - "traefik.http.routers.laravel.entrypoints=web"
      - "traefik.http.services.laravel.loadbalancer.server.port=80"
      - "traefik.http.services.laravel.loadbalancer.server.scheme=http"

Redis

Example: docker-compose.yml

services:
  redis:
    image: redis:7.2

Example: docker-compose.dev.yml

services:
  redis:
    command: "redis-server --appendonly yes --requirepass mysupersecretredispassword"
    volumes:
      - ./.infrastructure/volume_data/redis/data:/data
    ports:
      - "6379:6379"
    networks:
      - development

networks:
  development:

Soketi

See the Soketi documentation for selecting the correct version.

Example: docker-compose.yml

services:
  socket:
    image: quay.io/soketi/soketi:1.6-16

Example: docker-compose.dev.yml

services:
  socket:
    environment:
      DEBUG: '1'
      DB_REDIS_HOST: "redis"
      DB_REDIS_PASSWORD: "redispassword"
      DB_REDIS_KEY_PREFIX: "socketi"
    networks: 
      - development
networks:
  development:

Using with Traefik

The docker-compose.yml examples above exposes the HTTP server on port 80. If you use something like Traefik, you would replace the ports with labels, like this:

Laravel Traefik Labels Example

    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.soketi.rule=HostRegexp(`soketi.dev.test`)"
      - "traefik.http.routers.soketi.entrypoints=websecure"
      - "traefik.http.routers.soketi.tls=true"
      - "traefik.http.services.soketi.loadbalancer.server.port=6001"
      - "traefik.http.services.soketi.loadbalancer.server.scheme=http"

Traefik

Traefik is a reverse proxy and is great for terminating SSL for any service that supports HTTP/HTTPS. Since Traefik is such an important service, there can be a little more configuration in order to get it to function well.

Example: docker-compose.yml

services:
  traefik:
    image: traefik:v3.1

Example: docker-compose.dev.yml

  traefik:
    networks:
      development:
        aliases:
          - myapp.dev.test
    ports:
      - "80:80"
      - "443:443"
    volumes:
      # Add Docker as a mounted volume, so that Traefik can read the labels of other services
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./.infrastructure/conf/traefik/dev/traefik.yml:/traefik.yml:ro
      - ./.infrastructure/conf/traefik/dev/traefik-certs.yml:/traefik-certs.yml
      - ./.infrastructure/conf/traefik/dev/certificates/:/certificates

Example: Development configuration at `./.infrastructure/conf/traefik/dev/traefik.yml`

# Allow self-signed certificates
serversTransport:
  insecureSkipVerify: true

providers:
  docker:
    exposedByDefault: false
  file:
      filename: /traefik-certs.yml
      watch: true
entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entrypoint:
          to: websecure
          scheme: https

  websecure:
    address: ":443"

accessLog: {}
log:
  level: ERROR

api:
  dashboard: true
  insecure: true

Example: Development configuration at `./.infrastructure/conf/traefik/dev/traefik-certs.yml`

tls:
  stores:
    default:
      defaultCertificate:
        certFile: /certificates/local-dev.pem
        keyFile: /certificates/local-dev-key.pem
  certificates:
    - certFile: /certificates/local-dev.pem
      keyFile: /certificates/local-dev-key.pem
      stores:
        - default

Trusting self-signed certificates

If you use spin init or spin new to create your project, we ship a local-dev keypair. This is signed by the Server Side Up Certificate Authority. If you'd like to trust this CA, you need to install the CA Root on your machine (only do this if you trust our process): https://serversideup.net/ca/.