Skip to content

bug: Container permission issues with bind-mounted volumes #50

@nfebe

Description

@nfebe

Problem

When deploying containerized applications (like Laravel), bind-mounted storage directories can have permission mismatches between the host and container users, causing runtime errors like:

file_put_contents(/var/www/html/storage/framework/views/xxx.php): Failed to open stream: Permission denied

Root Cause

  1. Agent creates top-level mount directories as 0777 (discovery.go:281-286), but framework-specific subdirectories are created by containers at runtime
  2. Container users (e.g., www-data UID 82) don't match host users, so subdirectories created inside containers may have incorrect ownership
  3. Framework subdirectories are not pre-created - Laravel expects storage/framework/views/, storage/framework/cache/, storage/logs/, bootstrap/cache/ to exist

Current Behavior

// discovery.go:281-286
fullPath := filepath.Join(deploymentPath, hostPath)
if err := os.MkdirAll(fullPath, 0777); err != nil {
    return err
}

Only the top-level mount path is created. Subdirectories must be created by the container, which may fail due to permission issues.

Proposed Solution

Option 1: Pre-create framework-specific subdirectories

Add framework detection to createBindMountDirs() and pre-create known subdirectories:

// For Laravel deployments
laravelStorageDirs := []string{
    "framework/cache",
    "framework/sessions",
    "framework/views",
    "logs",
}

if isLaravelMount(hostPath) {
    for _, subdir := range laravelStorageDirs {
        subdirPath := filepath.Join(fullPath, subdir)
        if err := os.MkdirAll(subdirPath, 0777); err != nil {
            return err
        }
    }
}

Option 2: Add init container support

Allow templates to specify an init script that runs before the main container:

# metadata.yml
init:
  - mkdir -p /var/www/html/storage/framework/{cache,sessions,views}
  - mkdir -p /var/www/html/storage/logs
  - chown -R www-data:www-data /var/www/html/storage

Option 3: Template-level mount configuration

Extend metadata.yml mount definitions to include subdirectories:

mounts:
  - id: storage
    container_path: /app/storage
    subdirectories:
      - framework/cache
      - framework/sessions
      - framework/views
      - logs
    permissions: "0777"
    owner: "www-data"

Additional Issues Found

  1. Permission mode inconsistency: discovery.go uses 0777, files/manager.go uses 0755
  2. Named volumes in templates: WordPress template uses named volumes instead of bind mounts, violating flat-file principle
  3. No UID/GID mapping: Container user UIDs are not mapped to host users

Workaround (Current)

Users must manually fix permissions on the host after deployment:

# Find container user UID
docker compose exec app id www-data
# uid=82(www-data) gid=82(www-data)

# Fix permissions on host
chown -R 82:82 storage
chmod -R 775 storage

Affected Files

  • internal/docker/discovery.go - Volume creation logic
  • internal/files/manager.go - Directory creation utilities
  • templates/laravel/metadata.yml - Laravel template mounts
  • templates/wordpress/metadata.yml - WordPress template (uses named volumes)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions