IPERAMUNA.COM
Managing Dev and Production Composer Packages: A Global Configuration Approach
Article February 11, 2026

Managing Dev and Production Composer Packages: A Global Configuration Approach

Learn how to seamlessly switch between local development and production Composer packages using global configuration. Maintain clean composer.json files while developing packages alongside your main application.

As a PHP/Laravel composer package developer, you've likely faced this common dilemma: you're actively developing a package while simultaneously using it in your main application. You need to test changes immediately in your local environment, but in production, you want to use the stable, published version from Packagist. How do you manage this without constantly editing composer.json or maintaining separate configuration files?

The solution lies in Composer's global configuration combined with path repositories. This approach keeps your composer.json clean and production-ready while enabling seamless local development.

The Challenge

When developing a package within your main application, you typically have two conflicting needs:

  1. Local Development: You want Composer to symlink to your local package source so changes are immediately reflected
  2. Production Deployment: You want Composer to pull the stable release from Packagist

The traditional approach involves adding a path repository to your composer.json:

{
    "repositories": [
        {
            "type": "path",
            "url": "./packages/my-package"
        }
    ],
    "require": {
        "vendor/my-package": "^1.0"
    }
}

The problem? When you deploy to production, this path repository configuration remains in your composer.json. If the local path doesn't exist on the production server, Composer fails. Even if you .gitignore the packages directory, you're left with a broken configuration.

The Solution: Global Composer Configuration

Composer supports global configuration that applies to all projects on your machine. By moving your path repository configuration to the global level, you can keep your project's composer.json clean and production-ready.

Step 1: Clean Your Project's composer.json

First, ensure your composer.json uses the version constraint but no custom repositories:

{
    "require": {
        "iperamuna/laravel-self-deploy": "^1.0"
    }
}

This configuration works perfectly in production—Composer will fetch version ^1.0 from Packagist.

Step 2: Configure Global Path Repository

On your local development machine only, run:

composer config --global repositories.my-package path /absolute/path/to/packages/my-package

For example, if you're developing the laravel-self-deploy package:

composer config --global repositories.laravel-self-deploy path /Users/username/projects/myapp/packages/iperamuna/laravel-self-deploy

This adds the path repository to your global Composer configuration file (~/.composer/config.json or ~/.config/composer/config.json on Linux/Mac).

Step 3: Verify the Configuration

You can view your global configuration:

composer config --global --list

Or inspect the configuration file directly:

cat ~/.composer/config.json

You should see something like:

{
    "repositories": {
        "laravel-self-deploy": {
            "type": "path",
            "url": "/Users/username/projects/myapp/packages/iperamuna/laravel-self-deploy"
        }
    }
}

Step 4: Install or Update

Now, when you run composer install or composer update on your local machine:

  1. Composer checks your global configuration
  2. Finds the path repository definition
  3. Symlinks the local package instead of downloading from Packagist
  4. Your changes are immediately available in your application

Step 5: Deploy to Production

On your production server:

  1. The global Composer configuration doesn't exist (or doesn't include your local path)
  2. Composer only sees the "iperamuna/laravel-self-deploy": "^1.0" requirement
  3. It fetches the package from Packagist as expected
  4. Everything works seamlessly

How It Works

Composer's dependency resolution follows this priority order:

  1. Project-level repositories (defined in your project's composer.json)
  2. Global repositories (defined in ~/.composer/config.json)
  3. Packagist (the default public repository)

By defining path repositories globally, you're essentially saying: "On this machine, if you need this package, use this local path first." Production servers don't have this configuration, so they skip straight to Packagist.

Best Practices

1. Use Absolute Paths

Always use absolute paths in your global configuration to avoid ambiguity:

# Good
composer config --global repositories.my-package path /Users/username/projects/app/packages/my-package

# Avoid relative paths
composer config --global repositories.my-package path ./packages/my-package

2. Match Version Constraints

Ensure your package's version (whether in git tags or composer.json) satisfies the version constraint in your main application. For local development packages without explicit versions, Composer treats them as dev-main or dev-master.

If needed, you can alias the version:

{
    "require": {
        "vendor/my-package": "dev-main as 1.0.0"
    }
}

3. Document the Setup

Add a note to your project's README for other developers:

## Local Package Development

If you're developing the `iperamuna/laravel-self-deploy` package locally, configure a global path repository:

\```bash
composer config --global repositories.laravel-self-deploy path /absolute/path/to/packages/iperamuna/laravel-self-deploy
\```

4. Keep composer.json Clean

Never commit path repositories to your project's composer.json. This ensures:

  • Production deployments are clean and predictable
  • New team members don't encounter path-related errors
  • CI/CD pipelines work without modification

5. Team Coordination

If multiple developers work on the same package, each developer needs to:

  1. Clone the package to their preferred location
  2. Configure their own global path repository
  3. Use the same version constraint in composer.json

Removing Global Configuration

If you later want to use the Packagist version even locally (e.g., for testing), simply remove the global configuration:

composer config --global --unset repositories.my-package

Then run:

composer update vendor/my-package

Composer will now fetch the package from Packagist.

Real-World Example: Laravel Self Deploy

Let's walk through a complete example using the iperamuna/laravel-self-deploy package:

Project Structure:

/Users/johndoe/projects/myapp/
├── app/
├── packages/
│   └── iperamuna/
│       └── laravel-self-deploy/  # Local package source
├── vendor/
└── composer.json

Step 1: Clean composer.json:

{
    "require": {
        "iperamuna/laravel-self-deploy": "^1.0"
    }
}

Step 2: Configure globally (local machine only):

composer config --global repositories.laravel-self-deploy path /Users/johndoe/projects/myapp/packages/iperamuna/laravel-self-deploy

Step 3: Install dependencies:

composer install

Output:

- Installing iperamuna/laravel-self-deploy (1.0.0): Symlinking from /Users/johndoe/projects/myapp/packages/iperamuna/laravel-self-deploy

Step 4: Make changes to the package: Edit files in packages/iperamuna/laravel-self-deploy/ and they're immediately available in your application.

Step 5: Deploy to production: Your production server runs composer install and sees:

- Installing iperamuna/laravel-self-deploy (1.0.0): Downloading from packagist

Perfect! No configuration changes needed.

Benefits of This Approach

Clean Version Control: composer.json stays production-ready
Zero Production Impact: No accidental local path references
Flexible Development: Easy to switch between local and Packagist versions
Team-Friendly: Each developer configures their own environment
CI/CD Compatible: Automated builds work without modification

Potential Pitfalls and Solutions

Pitfall 1: Forgetting to Configure Globally

Problem: You remove the path repository from composer.json but forget to add it globally.

Solution: Composer will use the Packagist version. Just configure globally and run composer update.

Pitfall 2: Different Package Locations per Developer

Problem: Team members have different directory structures.

Solution: This is actually a benefit! Each developer sets their own global path. Document the process in your README.

Pitfall 3: Stale Symlinks

Problem: You move or delete the local package directory but the global config still points to it.

Solution: Either update the path or remove the global configuration:

composer config --global --unset repositories.my-package

Conclusion

Managing both development and production versions of your Composer packages doesn't have to be painful. By leveraging Composer's global configuration for path repositories, you can:

  • Keep your project's composer.json clean and production-ready
  • Develop packages seamlessly alongside your main application
  • Deploy to production without configuration changes
  • Maintain flexibility across different development environments

This approach is particularly valuable for Laravel/PHP composer package maintainers, teams building private packages, or anyone who needs tight integration between package development and application code.

Give it a try on your next package, and enjoy a cleaner, more maintainable workflow!


Have you used Composer global configuration for package development? Share your experiences and tips in the comments below!