Error messages like “works on my machine” are often not just a joke. The cause is frequently differences in runtime versions: Node.js on the developer's laptop differs from the server, or the PHP used on the local machine is newer than the version on hosting.
Runtime is the software that executes application code. Node.js runs JavaScript outside the browser, while PHP powers many websites and web applications. When versions differ, features, function behaviors, and even how libraries operate can change.
This issue becomes increasingly important because runtimes have support cycles. PHP, for example, divides the lifespan of each version branch into active support, security support, and then end of life. Node.js also distinguishes between Current and Long-Term Support (LTS) releases. This means that choosing a version is not just about using the latest, but also about security and compatibility with the project.
Why can version differences break applications?
Imagine a recipe written for an oven with a maximum temperature of 250 degrees. If the same recipe is run in an oven that only reaches 200 degrees, the result will certainly differ. Runtimes work similarly: code and dependencies expect certain capabilities from the environment in which they run.
Small differences can lead to several issues:
- Certain functions are available in the new version but not in the old version.
- Libraries require a higher minimum runtime version.
- Default behaviors change after an upgrade.
- PHP extensions or Node.js modules are not installed in the same way.
- Installation commands yield different dependency versions.
As a result, an application may look fine during development but fail when moved to a server, another team member's computer, or a testing environment.
Don't just memorize versions
The first step is to make runtime versions part of the project, not just personal knowledge of one individual. Every project should specify the required versions within the repository.
For Node.js projects, basic information can usually be included in files like .nvmrc or through the engines section in package.json. A simple example:
{
"engines": {
"node": ">=24 <27"
}
}This format is not a substitute for testing but provides clear guidance to team members and deployment services. For PHP projects, the minimum version can be noted in composer.json through the require section:
{
"require": {
"php": "^8.3"
}
}The version numbers above are just examples. The correct version should align with the application's needs, the libraries used, and the versions available on the server.
Use a version manager for switching projects
If working on multiple projects, manually installing one runtime version and then switching it every time you change projects can be tedious and error-prone. A version manager helps install multiple versions side by side and select the active version according to project needs.
For Node.js, developers often use tools like nvm or other version managers. For PHP, available options depend on the operating system, such as separate version packages, Docker, or specialized runtime management tools. The principle is the same: project versions are determined by configuration, not by user guesswork.
The workflow can be simplified as follows:
- Read the version specified by the project.
- Install that version if it is not already available.
- Activate the version when entering the project directory.
- Run version checks before installing dependencies.
Basic check commands should also become a habit:
node --version
npm --version
php --version
composer check-platform-reqsNot all commands need to be run in every project. Use those that are relevant to the application stack. The goal is to identify discrepancies before issues arise during deployment.
Differentiate between runtime versions and dependency versions
Runtime is not the only source of problems. Applications also rely on third-party packages, such as JavaScript libraries or PHP packages. Therefore, projects require two layers of locking.
The first layer is the runtime version: Node.js, PHP, or Python that runs the application. The second layer is the dependency version: packages installed by npm, Composer, or other package managers.
Files like package-lock.json and composer.lock help record the exact versions of packages being used. Do not delete lock files just because the installation process seems easier. These files function like a detailed shopping list: not only naming the main brands but also specifying the version of each ingredient needed.
However, lock files are not an absolute guarantee. Dependencies still need to be updated systematically and tested. Locking versions helps with reproducibility, but it does not mean the application is free from bugs or vulnerabilities.
Choose supported versions, not just the latest
The latest version is not necessarily the best choice for production applications. New releases may bring important features but can also change old behaviors or may not be supported by all libraries.
On the other hand, using a version that has reached end of life can increase security risks because fixes for critical bugs are no longer available. The official PHP documentation provides a list of branches that are still supported and their end-of-support dates. Node.js also provides information about Current, Active LTS, and Maintenance statuses.
A more sensible approach is to choose a version that:
- Is still receiving security updates.
- Is supported by major frameworks and libraries.
- Is available in the production environment.
- Can be tested by the team before widespread use.
“Latest” is a matter of timing. “Compatible and still supported” is a technical decision.
What you can do now
- Document runtime versions. Add the required Node.js or PHP information to the project documentation.
- Check the server. Run version checks in the production environment, not just on laptops.
- Use lock files. Keep
package-lock.jsonorcomposer.lockin the repository if they are used in the deployment workflow. - Add automated checks. CI can fail early if the runtime versions do not match.
- Test upgrades in a separate environment. Do not make the production server the first place to try new versions.
- Plan migrations. If the version in use is nearing end of support, create an upgrade schedule before being forced to switch due to emergencies.
Maintaining runtime consistency may sound like a small task, but its impact is significant. Teams spend less time guessing the causes of errors, deployment processes become more predictable, and upgrade decisions can be made based on application needs—not panic after the system fails to run.
Sources & further reading
- PHP: Supported Versions
- PHP: Unsupported Branches
- Node.js Releases
- Node.js: Evolving the Release Schedule
– Rio Yotto @rioyotto
