Add Common 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 
developmentand properly created in your Docker Compose file - You have the proper configurations made available in your 
.infrastructurefolder with the.gitignoreproperly configured 
MariaDB
Example: docker-compose.yml
version: '3.8'
services:
  mariadb: 
    image: mariadb:10.11
Example: docker-compose.dev.yml
version: '3.8'
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
version: '3.8'
services:
  meilisearch:
    image: getmeili/meilisearch:v1.5
    environment:
      MEILI_NO_ANALYTICS: "true"
Example: docker-compose.dev.yml
version: '3.8'
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
version: '3.8'
services:
  mysql: 
    image: mysql:8.2
Example: docker-compose.dev.yml
version: '3.8'
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
version: '3.8'
services:
  node:
    image: node:20
    working_dir: /usr/src/app
Example: docker-compose.dev.yml
version: '3.8'
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.
Notice how this project is using build instead of just calling an image directly. See the Dockerfile example why we do this.
Example: docker-compose.yml
version: '3.8'
services:
  php:
    build:
      context: . # Look for Dockerfile in the project root
      target: base
Example: docker-compose.dev.yml
version: '3.8'
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:beta-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
version: '3.8'
services:
  redis:
    image: redis:7.2
Example: docker-compose.dev.yml
version: '3.8'
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
version: '3.8'
services:
  socket:
    image: quay.io/soketi/soketi:1.6-16
Example: docker-compose.dev.yml
version: '3.8'
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
version: '3.8'
services:
  traefik:
    image: traefik:v2.10
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:
    network: development
    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 # Be sure these paths to your keys are accurate
        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/.