chore: fix build dependencies and frontend config

Migrate Docker SDK from github.com/docker/docker (+incompatible)
to github.com/moby/moby/client v0.3.0 + moby/moby/api v1.54.0
(proper Go modules). Adapt all container/image/network operations
to the new moby API (Filters, ContainerListOptions, PullResponse,
InspectResult, etc.). Add AsyncTriggerDeploy runDeploy method.

Fix SvelteKit build: disable prerender, set strict=false for SPA,
bump vite-plugin-svelte to v5 for vite 6 compat.

Add .dockerignore to exclude .git, node_modules, plans.
This commit is contained in:
2026-03-28 13:13:45 +03:00
parent 179be231c2
commit 652229c67f
9 changed files with 301 additions and 82 deletions
+74 -1
View File
@@ -17,7 +17,7 @@ import (
"github.com/alexei/docker-watcher/internal/notify"
"github.com/alexei/docker-watcher/internal/npm"
"github.com/alexei/docker-watcher/internal/store"
"github.com/docker/docker/api/types/mount"
"github.com/moby/moby/api/types/mount"
"github.com/google/uuid"
)
@@ -118,6 +118,79 @@ func (d *Deployer) AsyncTriggerDeploy(ctx context.Context, projectID, stageID, i
return deploy.ID, nil
}
// runDeploy is the internal deploy pipeline used by AsyncTriggerDeploy.
// It assumes the deploy record already exists and project/stage are validated.
func (d *Deployer) runDeploy(ctx context.Context, project store.Project, stage store.Stage, deployID string, imageTag string) error {
settings, err := d.store.GetSettings()
if err != nil {
if updateErr := d.store.UpdateDeployStatus(deployID, "failed", err.Error()); updateErr != nil {
slog.Warn("update deploy status", "error", updateErr)
}
return fmt.Errorf("get settings: %w", err)
}
slog.Info("starting deploy",
"deploy_id", deployID,
"project", project.Name,
"stage", stage.Name,
"tag", imageTag,
)
d.logDeploy(deployID, fmt.Sprintf("Starting deploy of %s:%s for project %s, stage %s", project.Image, imageTag, project.Name, stage.Name), "info")
// Enforce max_instances before deploying.
if err := d.enforceMaxInstances(ctx, stage, deployID, settings); err != nil {
d.logDeploy(deployID, fmt.Sprintf("Failed to enforce max instances: %v", err), "error")
}
var containerID string
var npmProxyID int
var instanceID string
var deployErr error
if stage.MaxInstances == 1 {
containerID, npmProxyID, instanceID, deployErr = d.blueGreenDeploy(ctx, project, stage, settings, deployID, imageTag)
} else {
containerID, npmProxyID, instanceID, deployErr = d.executeDeploy(ctx, project, stage, settings, deployID, imageTag)
}
if deployErr != nil {
d.logDeploy(deployID, fmt.Sprintf("Deploy failed: %v", deployErr), "error")
d.publishDeployStatus(deployID, project.ID, stage.ID, imageTag, "failed", deployErr.Error())
d.rollback(ctx, deployID, containerID, npmProxyID, instanceID)
d.notifier.Send(settings.NotificationURL, notify.Event{
Type: "deploy_failure",
Project: project.Name,
Stage: stage.Name,
ImageTag: imageTag,
Error: deployErr.Error(),
})
return fmt.Errorf("deploy failed: %w", deployErr)
}
if err := d.store.UpdateDeployStatus(deployID, "success", ""); err != nil {
slog.Warn("update deploy status to success", "error", err)
}
d.publishDeployStatus(deployID, project.ID, stage.ID, imageTag, "success", "")
subdomain := d.buildSubdomain(project, stage, settings, imageTag)
fullURL := fmt.Sprintf("https://%s.%s", subdomain, settings.Domain)
d.logDeploy(deployID, fmt.Sprintf("Deploy successful: %s", fullURL), "info")
d.notifier.Send(settings.NotificationURL, notify.Event{
Type: "deploy_success",
Project: project.Name,
Stage: stage.Name,
ImageTag: imageTag,
Subdomain: subdomain,
URL: fullURL,
})
return nil
}
// TriggerDeploy is the synchronous entry point for deployments (used by poller and webhook).
// It orchestrates the full flow: pull image -> create container -> start -> configure proxy -> health check.
// On failure, it rolls back (removes container, deletes proxy host, updates status).