Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message
Spin Laravel Development Deployment
Spin Laravel Development Deployment
serversideup/spin
claude-code
deployment
docker-swarm
github-actions
spin-cli
ci-cd
docker-compose
ssh

Both strategies deploy to Docker Swarm with zero-downtime updates.

Install
php artisan boost:add-skill serversideup/spin

Save this content to: .claude/skills/spin-laravel-development-deployment/SKILL.md

# Deployment

## Table of contents

- [Choosing a strategy](#choosing-a-strategy)
- [spin deploy](#spin-deploy)
- [GitHub Actions](#github-actions)
- [Production compose patterns](#production-compose-patterns)
- [Per-environment env files](#per-environment-env-files)
- [spin.yml server configuration](#spinyml-server-configuration)

---

## Choosing a strategy

| Strategy | Best for | Complexity |
|----------|----------|------------|
| `spin deploy` | Solo developers, simple deployments | Low |
| GitHub Actions | Teams, CI/CD pipelines | Medium |

Both strategies deploy to Docker Swarm with zero-downtime updates.

## spin deploy

Builds locally, pushes via SSH tunnel to the server, deploys as a Docker Swarm stack — all in one command:

```bash
spin deploy production
spin deploy staging
```

### How it works

1. Loads `.env` (or `.env.<environment>`)
2. Finds Dockerfiles and builds images with `docker buildx`
3. Starts a temporary local Docker registry
4. Pushes built images to the local registry
5. Opens an SSH tunnel to the server
6. Server pulls images through the tunnel
7. Deploys the Docker Swarm stack
8. Cleans up registry and tunnel

### Prerequisites

- Server provisioned with `spin provision`
- `.spin.yml` configured with server addresses
- SSH access to the server

### Options

| Option | Short | Default | Description |
|--------|-------|---------|-------------|
| `--compose-file` | `-c` | `docker-compose.yml,docker-compose.prod.yml` | Compose files |
| `--port` | `-p` | `22` | SSH port |
| `--user` | `-u` | `deploy` | SSH user (dedicated non-sudo user created by `spin provision`) |
| `--upgrade` | `-U` | `false` | Force Ansible collection upgrade |

### Environment variables

| Variable | Default | Description |
|----------|---------|-------------|
| `SPIN_BUILD_PLATFORM` | `linux/amd64` | Build platform |
| `SPIN_BUILD_TAG` | Current timestamp | Image tag |
| `SPIN_REGISTRY_PORT` | `5080` | Local registry port |
| `SPIN_PROJECT_NAME` | `spin` | Project name for the stack |
| `SPIN_SSH_USER` | `deploy` | SSH user (same as `--user`) |

### Compose variables available after deploy

| Variable | Example | Description |
|----------|---------|-------------|
| `SPIN_IMAGE_DOCKERFILE` | `127.0.0.1:5080/dockerfile:20260819120000` | Built image reference (one `SPIN_IMAGE_*` variable per Dockerfile) |
| `SPIN_MD5_HASH_*` | `abcdef123456` | MD5 of config files (for Swarm config rotation) |
| `SPIN_DEPLOYMENT_ENVIRONMENT` | `production` | Target environment name |
| `SPIN_APP_DOMAIN` | `example.com` | Extracted from `APP_URL` in `.env` |

## GitHub Actions

Open source actions for automated CI/CD:

- [**docker-build-action**](https://github.com/marketplace/actions/docker-build-action) — Build and publish Docker images
- [**docker-swarm-deploy-github-action**](https://github.com/marketplace/actions/docker-swarm-deploy-github-action) — Deploy to Docker Swarm

### Setup with Spin

```bash
spin configure gha <environment>
```

This configures GitHub repository secrets and SSH keys for deployment.

### Security considerations

- Deployment SSH keys are stored in GitHub Actions secrets
- Consider self-hosted runners for tighter security
- Consider IP-restricted SSH access for production servers

### Zero-downtime requirements

For zero-downtime deployments to work:
- Traefik (or another reverse proxy) must be configured
- Container health checks must be defined
- Docker Swarm update configs must use `order: start-first`
- Services must handle graceful shutdown

## Production compose patterns

Key settings for `docker-compose.prod.yml`:

```yaml
services:
  php:
    image: ${SPIN_IMAGE_DOCKERFILE}
    environment:
      PHP_OPCACHE_ENABLE: "1"
      AUTORUN_ENABLED: "true"
      SSL_MODE: full
      HEALTHCHECK_PATH: "/up"
    deploy:
      replicas: 1
      update_config:
        failure_action: rollback
        parallelism: 1
        delay: 5s
        order: start-first
      rollback_config:
        parallelism: 0
        order: stop-first
      restart_policy:
        condition: any
        delay: 10s
        max_attempts: 3
        window: 120s
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.app.rule=Host(`${SPIN_APP_DOMAIN}`)"
      - "traefik.http.routers.app.entrypoints=websecure"
      - "traefik.http.routers.app.tls=true"
      - "traefik.http.routers.app.tls.certresolver=letsencryptresolver"
      - "traefik.http.services.app.loadbalancer.server.port=8443"
      - "traefik.http.services.app.loadbalancer.server.scheme=https"
      - "traefik.http.services.app.loadbalancer.healthcheck.path=/up"
      - "traefik.http.services.app.loadbalancer.healthcheck.interval=30s"
      - "traefik.http.services.app.loadbalancer.healthcheck.scheme=http"

traefik:
    configs:
      - source: traefik
        target: /etc/traefik/traefik.yml

configs:
  traefik:
    name: "traefik-${SPIN_MD5_HASH_TRAEFIK_YML}.yml"
    file: ./.infrastructure/conf/traefik/prod/traefik.yml

volumes:
  storage_private:
  storage_public:
  storage_sessions:
  storage_logs:
  database_sqlite:

networks:
  web-public:
```

## Per-environment env files

Create separate `.env` files for each deployment environment:

```
.env                # Local development (default)
.env.production     # Production settings
.env.staging        # Staging settings
```

`spin deploy production` sets `APP_ENV=production`, and Laravel automatically loads `.env.production`.

Add `.env.*` to `.gitignore`.

## .spin.yml server configuration

The `.spin.yml` file defines your server inventory. Basic structure:

```yaml
users:
  - username: alice
    name: Alice
    groups: ["sudo"]
    authorized_ssh_keys:
      - "ssh-ed25519 AAAA... [email protected]"

servers:
  - server_name: server01
    environment: production
    address: 203.0.113.10

environments:
  production:
    docker_user: alice
```

For provider-managed servers (Hetzner, DigitalOcean, Vultr), Spin creates the server and fills in the `address` automatically.

Encrypt sensitive values with `spin vault encrypt .spin.yml`.

Deployment

Table of contents


Choosing a strategy

Strategy Best for Complexity
spin deploy Solo developers, simple deployments Low
GitHub Actions Teams, CI/CD pipelines Medium

Both strategies deploy to Docker Swarm with zero-downtime updates.

spin deploy

Builds locally, pushes via SSH tunnel to the server, deploys as a Docker Swarm stack — all in one command:

spin deploy production
spin deploy staging

How it works

  1. Loads .env (or .env.<environment>)
  2. Finds Dockerfiles and builds images with docker buildx
  3. Starts a temporary local Docker registry
  4. Pushes built images to the local registry
  5. Opens an SSH tunnel to the server
  6. Server pulls images through the tunnel
  7. Deploys the Docker Swarm stack
  8. Cleans up registry and tunnel

Prerequisites

  • Server provisioned with spin provision
  • .spin.yml configured with server addresses
  • SSH access to the server

Options

Option Short Default Description
--compose-file -c docker-compose.yml,docker-compose.prod.yml Compose files
--port -p 22 SSH port
--user -u deploy SSH user (dedicated non-sudo user created by spin provision)
--upgrade -U false Force Ansible collection upgrade

Environment variables

Variable Default Description
SPIN_BUILD_PLATFORM linux/amd64 Build platform
SPIN_BUILD_TAG Current timestamp Image tag
SPIN_REGISTRY_PORT 5080 Local registry port
SPIN_PROJECT_NAME spin Project name for the stack
SPIN_SSH_USER deploy SSH user (same as --user)

Compose variables available after deploy

Variable Example Description
SPIN_IMAGE_DOCKERFILE 127.0.0.1:5080/dockerfile:20260819120000 Built image reference (one SPIN_IMAGE_* variable per Dockerfile)
SPIN_MD5_HASH_* abcdef123456 MD5 of config files (for Swarm config rotation)
SPIN_DEPLOYMENT_ENVIRONMENT production Target environment name
SPIN_APP_DOMAIN example.com Extracted from APP_URL in .env

GitHub Actions

Open source actions for automated CI/CD:

Setup with Spin

spin configure gha <environment>

This configures GitHub repository secrets and SSH keys for deployment.

Security considerations

  • Deployment SSH keys are stored in GitHub Actions secrets
  • Consider self-hosted runners for tighter security
  • Consider IP-restricted SSH access for production servers

Zero-downtime requirements

For zero-downtime deployments to work:

  • Traefik (or another reverse proxy) must be configured
  • Container health checks must be defined
  • Docker Swarm update configs must use order: start-first
  • Services must handle graceful shutdown

Production compose patterns

Key settings for docker-compose.prod.yml:

services:
  php:
    image: ${SPIN_IMAGE_DOCKERFILE}
    environment:
      PHP_OPCACHE_ENABLE: "1"
      AUTORUN_ENABLED: "true"
      SSL_MODE: full
      HEALTHCHECK_PATH: "/up"
    deploy:
      replicas: 1
      update_config:
        failure_action: rollback
        parallelism: 1
        delay: 5s
        order: start-first
      rollback_config:
        parallelism: 0
        order: stop-first
      restart_policy:
        condition: any
        delay: 10s
        max_attempts: 3
        window: 120s
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.app.rule=Host(`${SPIN_APP_DOMAIN}`)"
      - "traefik.http.routers.app.entrypoints=websecure"
      - "traefik.http.routers.app.tls=true"
      - "traefik.http.routers.app.tls.certresolver=letsencryptresolver"
      - "traefik.http.services.app.loadbalancer.server.port=8443"
      - "traefik.http.services.app.loadbalancer.server.scheme=https"
      - "traefik.http.services.app.loadbalancer.healthcheck.path=/up"
      - "traefik.http.services.app.loadbalancer.healthcheck.interval=30s"
      - "traefik.http.services.app.loadbalancer.healthcheck.scheme=http"

traefik:
    configs:
      - source: traefik
        target: /etc/traefik/traefik.yml

configs:
  traefik:
    name: "traefik-${SPIN_MD5_HASH_TRAEFIK_YML}.yml"
    file: ./.infrastructure/conf/traefik/prod/traefik.yml

volumes:
  storage_private:
  storage_public:
  storage_sessions:
  storage_logs:
  database_sqlite:

networks:
  web-public:

Per-environment env files

Create separate .env files for each deployment environment:

.env                # Local development (default)
.env.production     # Production settings
.env.staging        # Staging settings

spin deploy production sets APP_ENV=production, and Laravel automatically loads .env.production.

Add .env.* to .gitignore.

.spin.yml server configuration

The .spin.yml file defines your server inventory. Basic structure:

users:
  - username: alice
    name: Alice
    groups: ["sudo"]
    authorized_ssh_keys:
      - "ssh-ed25519 AAAA... [email protected]"

servers:
  - server_name: server01
    environment: production
    address: 203.0.113.10

environments:
  production:
    docker_user: alice

For provider-managed servers (Hetzner, DigitalOcean, Vultr), Spin creates the server and fills in the address automatically.

Encrypt sensitive values with spin vault encrypt .spin.yml.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console