Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions docs-v1/content/en/docs/pipeline-stages/deployers/docker.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,129 @@ the application image `my-image` to the local Docker daemon:
{{< alert title="Note" >}}
Images listed to be deployed with the `docker` deployer **must also have a corresponding build artifact built by Skaffold.**
{{< /alert >}}

## Deploying with Docker Compose

Skaffold can deploy your application using Docker Compose instead of individual containers.
This is useful when your application is already configured with a `docker-compose.yml` file
and you want to leverage Compose's features like service dependencies, networks, and volumes.

### Configuration

To deploy using Docker Compose, set `useCompose: true` in the `docker` deploy configuration:

```yaml
deploy:
docker:
useCompose: true
images:
- my-app
```

### How it works

When `useCompose` is enabled, Skaffold:

1. Reads your `docker-compose.yml` file (or a custom file specified via environment variable)
2. Builds images as specified in your `build` section
3. Automatically replaces image names in the compose file with the built image tags
4. Creates a temporary compose file with the updated images
5. Runs `docker compose up -d` with a unique project name (`skaffold-{runID}`)
6. On cleanup, runs `docker compose down --volumes --remove-orphans`

### Image name mapping

**Important**: For Skaffold to correctly replace images in your compose file, the image names
in your `docker-compose.yml` must match (or be contained in) the image names specified in
the `build.artifacts` section.

For example, if your `skaffold.yaml` has:

```yaml
build:
artifacts:
- image: gcr.io/my-project/frontend-app
- image: gcr.io/my-project/backend-app
```

Your `docker-compose.yml` should use matching image names:

```yaml
version: '3.8'
services:
frontend:
image: frontend-app # Matches the suffix of gcr.io/my-project/frontend-app
backend:
image: backend-app # Matches the suffix of gcr.io/my-project/backend-app
```

Skaffold will replace `frontend-app` with `gcr.io/my-project/frontend-app:latest-abc123`
and `backend-app` with `gcr.io/my-project/backend-app:latest-def456`.

### Custom compose file location

By default, Skaffold looks for `docker-compose.yml` in the current directory.
You can specify a custom location using the `SKAFFOLD_COMPOSE_FILE` environment variable:

```bash
export SKAFFOLD_COMPOSE_FILE=path/to/my-compose.yml
skaffold dev
```

Or inline:

```bash
SKAFFOLD_COMPOSE_FILE=docker-compose.prod.yml skaffold run
```

### Example

Complete example configuration:

**skaffold.yaml:**
```yaml
apiVersion: skaffold/v4beta13
kind: Config
build:
artifacts:
- image: my-web-app
docker:
dockerfile: Dockerfile
deploy:
docker:
useCompose: true
images:
- my-web-app
```

**docker-compose.yml:**
```yaml
version: '3.8'
services:
web:
image: my-web-app
ports:
- "8080:8080"
environment:
- NODE_ENV=development
redis:
image: redis:7-alpine
ports:
- "6379:6379"
```

When you run `skaffold dev`, Skaffold will:
- Build `my-web-app` image
- Replace `my-web-app` in the compose file with the built tag (e.g., `my-web-app:latest-abc123`)
- Leave `redis:7-alpine` unchanged (not built by Skaffold)
- Deploy both services using `docker compose up`

### Limitations and Notes

- The compose file must have a valid `services` section
- Only images that are built by Skaffold will be replaced
- External images (like `postgres`, `redis`, etc.) are deployed as-is
- The compose project name is automatically generated as `skaffold-{runID}` to avoid conflicts
- Multiple Skaffold instances can run simultaneously without interfering with each other

For a complete working example, see [`examples/docker-compose-deploy`](https://github.com/GoogleContainerTools/skaffold/tree/main/examples/docker-compose-deploy).
6 changes: 6 additions & 0 deletions docs-v1/content/en/samples/deployers/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
deploy:
docker:
useCompose: true
images:
- my-web-app
- my-api-app
126 changes: 126 additions & 0 deletions docs-v2/content/en/docs/deployers/docker.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,129 @@ the application image `my-image` to the local Docker daemon:
{{< alert title="Note" >}}
Images listed to be deployed with the `docker` deployer **must also have a corresponding build artifact built by Skaffold.**
{{< /alert >}}

## Deploying with Docker Compose

Skaffold can deploy your application using Docker Compose instead of individual containers.
This is useful when your application is already configured with a `docker-compose.yml` file
and you want to leverage Compose's features like service dependencies, networks, and volumes.

### Configuration

To deploy using Docker Compose, set `useCompose: true` in the `docker` deploy configuration:

```yaml
deploy:
docker:
useCompose: true
images:
- my-app
```

### How it works

When `useCompose` is enabled, Skaffold:

1. Reads your `docker-compose.yml` file (or a custom file specified via environment variable)
2. Builds images as specified in your `build` section
3. Automatically replaces image names in the compose file with the built image tags
4. Creates a temporary compose file with the updated images
5. Runs `docker compose up -d` with a unique project name (`skaffold-{runID}`)
6. On cleanup, runs `docker compose down --volumes --remove-orphans`

### Image name mapping

**Important**: For Skaffold to correctly replace images in your compose file, the image names
in your `docker-compose.yml` must match (or be contained in) the image names specified in
the `build.artifacts` section.

For example, if your `skaffold.yaml` has:

```yaml
build:
artifacts:
- image: gcr.io/my-project/frontend-app
- image: gcr.io/my-project/backend-app
```

Your `docker-compose.yml` should use matching image names:

```yaml
version: '3.8'
services:
frontend:
image: frontend-app # Matches the suffix of gcr.io/my-project/frontend-app
backend:
image: backend-app # Matches the suffix of gcr.io/my-project/backend-app
```

Skaffold will replace `frontend-app` with `gcr.io/my-project/frontend-app:latest-abc123`
and `backend-app` with `gcr.io/my-project/backend-app:latest-def456`.

### Custom compose file location

By default, Skaffold looks for `docker-compose.yml` in the current directory.
You can specify a custom location using the `SKAFFOLD_COMPOSE_FILE` environment variable:

```bash
export SKAFFOLD_COMPOSE_FILE=path/to/my-compose.yml
skaffold dev
```

Or inline:

```bash
SKAFFOLD_COMPOSE_FILE=docker-compose.prod.yml skaffold run
```

### Example

Complete example configuration:

**skaffold.yaml:**
```yaml
apiVersion: skaffold/v4beta13
kind: Config
build:
artifacts:
- image: my-web-app
docker:
dockerfile: Dockerfile
deploy:
docker:
useCompose: true
images:
- my-web-app
```

**docker-compose.yml:**
```yaml
version: '3.8'
services:
web:
image: my-web-app
ports:
- "8080:8080"
environment:
- NODE_ENV=development
redis:
image: redis:7-alpine
ports:
- "6379:6379"
```

When you run `skaffold dev`, Skaffold will:
- Build `my-web-app` image
- Replace `my-web-app` in the compose file with the built tag (e.g., `my-web-app:latest-abc123`)
- Leave `redis:7-alpine` unchanged (not built by Skaffold)
- Deploy both services using `docker compose up`

### Limitations and Notes

- The compose file must have a valid `services` section
- Only images that are built by Skaffold will be replaced
- External images (like `postgres`, `redis`, etc.) are deployed as-is
- The compose project name is automatically generated as `skaffold-{runID}` to avoid conflicts
- Multiple Skaffold instances can run simultaneously without interfering with each other

For a complete working example, see [`examples/docker-compose-deploy`](https://github.com/GoogleContainerTools/skaffold/tree/main/examples/docker-compose-deploy).
6 changes: 6 additions & 0 deletions docs-v2/content/en/samples/deployers/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
deploy:
docker:
useCompose: true
images:
- my-web-app
- my-api-app
62 changes: 62 additions & 0 deletions integration/delete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package integration

import (
"context"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -133,6 +134,67 @@ func getContainers(ctx context.Context, t *testutil.T, deployedContainers []stri
return cl
}

func getComposeContainers(ctx context.Context, t *testutil.T, projectNamePrefix string, client docker.LocalDaemon) []types.Container {
t.Helper()

// List all containers
cl, err := client.ContainerList(ctx, container.ListOptions{
All: true,
})
t.CheckNoError(err)

// Filter containers by project name prefix
var result []types.Container
for _, c := range cl {
if project, ok := c.Labels["com.docker.compose.project"]; ok {
// Check if project name starts with the prefix (e.g., "skaffold-")
if strings.HasPrefix(project, projectNamePrefix) {
result = append(result, c)
}
}
}

return result
}

func TestDeleteDockerComposeDeployer(t *testing.T) {
tests := []struct {
description string
dir string
args []string
}{
{
description: "docker compose deployer",
dir: "testdata/docker-compose-deploy",
args: []string{},
},
}

for _, test := range tests {
testutil.Run(t, test.description, func(t *testutil.T) {
MarkIntegrationTest(t.T, CanRunWithoutGcp)
ctx := context.Background()

// Run skaffold to deploy with docker compose
skaffold.Run(test.args...).InDir(test.dir).RunOrFail(t.T)

// Verify containers are running
client := SetupDockerClient(t.T)
containers := getComposeContainers(ctx, t, "skaffold-", client)
if len(containers) == 0 {
t.T.Fatal("Expected at least one container to be deployed")
}

// Delete the deployment
skaffold.Delete(test.args...).InDir(test.dir).RunOrFail(t.T)

// Verify containers are deleted
containers = getComposeContainers(ctx, t, "skaffold-", client)
t.CheckDeepEqual(0, len(containers))
})
}
}

func TestDeleteNonExistedHelmResource(t *testing.T) {
var tests = []struct {
description string
Expand Down
42 changes: 42 additions & 0 deletions integration/dev_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,48 @@ func TestDevCancelWithDockerDeployer(t *testing.T) {
}
}

func TestDevCancelWithDockerComposeDeployer(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("graceful cancel doesn't work on windows")
}

tests := []struct {
description string
dir string
minContainers int
projectPrefix string
}{
{
description: "interrupt dev loop in Docker Compose deployer",
dir: "testdata/docker-compose-deploy",
minContainers: 1,
projectPrefix: "skaffold-",
},
}

for _, test := range tests {
t.Run(test.description, func(t *testing.T) {
MarkIntegrationTest(t, CanRunWithoutGcp)
p, err := skaffold.Dev().InDir(test.dir).StartWithProcess(t)
if err != nil {
t.Fatalf("error starting skaffold dev process")
}

if err = waitForComposeContainersRunning(t, test.projectPrefix, test.minContainers); err != nil {
t.Fatalf("failed waiting for containers: %v", err)
}

p.Signal(syscall.SIGINT)

state, _ := p.Wait()

if state.ExitCode() != 0 {
t.Fail()
}
})
}
}

func TestDevAPIBuildTrigger(t *testing.T) {
MarkIntegrationTest(t, CanRunWithoutGcp)

Expand Down
Loading