Home / Articles / Software
Software

Lockfile Is Not a Trash File: How to Keep Application Dependencies Consistent and Secure

Modern applications often rely on dozens or even thousands of third-party packages. By understanding the function of lockfiles, npm ci, and automatic dependency updates, developers can reduce the issues of 'it works on my machine'...

Lockfile Bukan File Sampah: Cara Menjaga Dependensi Aplikasi Tetap Konsisten dan Aman

Dependency issues often only become apparent when an application is moved to another computer or deployed to a production server. Code that worked fine yesterday suddenly fails to build because package versions have changed, transitive dependencies differ, or there are libraries with known security vulnerabilities.

This is where the lockfile comes into play. A lockfile is a file that records the exact versions of the packages used in a project, including indirect dependencies. In the Node.js ecosystem, this file is typically named package-lock.json. Many novice developers consider it an automatic file that can be deleted. In fact, for most projects, the lockfile is an essential part of the source code.

What is the actual function of a lockfile?

The package.json file usually stores the range of versions that can be used. For example, writing ^4.18.0 allows npm to choose minor or patch versions that are still considered compatible. This flexibility is useful, but it also means that installation results can change over time.

Today, a project might get version 4.18.2. A few weeks later, without any changes to the application code, the installation process may pick a newer version as long as it meets the specified rules. Dependencies from other packages can also change and bring different combinations of packages.

According to npm documentation, package-lock.json describes the exact dependency tree that was generated so that subsequent installations can produce the same package tree. In other words, package.json explains which packages can be used, while the lockfile records which packages are actually used. npm documentation explains that the lockfile should be kept in the repository so that team members, deployment servers, and CI systems receive consistent dependencies.

Don't delete the lockfile just because it looks large

The size of a lockfile can indeed reach thousands of lines. That doesn't mean its contents are useless. Each entry helps the package manager know the version, location of the package, and the relationships between dependencies.

The lockfile also makes it easier to review dependency changes. When someone updates a library, changes to this file can be seen in a pull request. The team can check whether the update only affects one package or also changes many transitive dependencies.

However, the lockfile is not a guarantee that all packages are secure. It only makes the composition of dependencies more predictable. Packages that are locked to older versions can still have vulnerabilities. Therefore, consistency and security should be treated as two complementary tasks.

Differentiate between npm install and npm ci

A common mistake in deployment pipelines is using the wrong command. npm install is designed to install packages while also resolving or updating the lockfile if necessary. If the contents of package.json and the lockfile do not match, npm may perform a re-resolution.

Meanwhile, npm ci is intended for clean and consistent installations in automated environments like CI or build servers. npm documents that this command requires package.json and the lockfile to be in sync. It is not meant to change the project manifest during the installation process.

A simple flow example on a developer's machine:

npm install
pm test

A stricter flow example for the pipeline:

npm ci
pm test
pm run build

This approach helps ensure that the build results come from the same versions of dependencies that have been approved by the team. If the lockfile is not in sync, the pipeline fails early. This is better than a successful deployment that produces different behavior on the server.

Lockfile and security: what’s the connection?

Each additional dependency expands the attack surface, which is the area that could potentially become a gateway for issues. Even packages that are not directly called by our code can bring in other dependencies that are also installed.

Therefore, conduct regular audits with the command:

npm audit
pm outdated

npm audit helps find known vulnerabilities in the dependency tree. The results still need to be read carefully: not all warnings have the same impact on the application, and automatic updates can sometimes trigger behavioral changes.

For projects stored on GitHub, Dependabot can alert when vulnerable dependencies are found and create a pull request for security updates. GitHub also provides a dependabot.yml configuration to manage the package ecosystem and update schedules. This feature is not a substitute for testing but can reduce the likelihood of forgetting important updates. GitHub's guide on securing dependencies explains the differences between alerts, security updates, and version updates.

What you can do now

  1. Commit the lockfile to the repository. For npm projects used as applications, do not include node_modules, but keep package-lock.json.
  2. Use agreed-upon versions of Node.js and npm. Files like .nvmrc or version settings in the pipeline can help reduce environmental differences.
  3. Use npm ci in CI and deployment. Ensure that the build process indeed starts from a clean installation.
  4. Review every change to the lockfile. Pay attention to new packages, major version changes, and installation scripts that have also changed.
  5. Schedule minor updates. Routine updates are usually easier to test than waiting months to perform a major upgrade all at once.
  6. Remove unused dependencies. Packages that are no longer needed only add to the project's size, installation time, and potential risks.

Don't chase “everything must be the latest version”

The latest version is not always the best choice on the first day of release. Updates need to be evaluated based on changelogs, compatibility, test results, and their impact on the application. For libraries that touch authentication, databases, payments, or build processes, manual checks remain important.

Healthy practices do not involve locking a project forever, but rather locking the current state being used and then updating it in a planned manner. The lockfile provides a clear reference point. Testing ensures that changes do not break features. Security audits help identify issues that are not visible from the application code itself.

For solo developers or small teams, these habits may feel like extra work. However, spending a few minutes to keep dependencies tidy is far cheaper than spending a day figuring out why the production build differs from the local laptop.

Sources & further reading

– Rio Yotto @rioyotto