# serverless-localstack: running a serverless stack on your own machine

> Deploying a serverless application normally means deploying it. This plugin changes that: it redirects every request the framework would make to a cloud service towards a local emulator, so that the deploy step becomes a local test and the whole feedback loop shrinks from minutes to seconds. The interesting parts are the details of how it stays out of the way in production, and how it makes the function code reload fast enough that you would want to use it.

**localstack/serverless-localstack** — ⚡ Serverless plugin for running against LocalStack

- Repository: https://github.com/localstack/serverless-localstack
- Stars: 541 · Forks: 93
- Language: JavaScript
- License: not declared
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/localstack-serverless-localstack

## What the plugin actually does

The plugin's job is one sentence long and it is the whole idea: it lets a serverless application be deployed and tested on your local machine, by redirecting any request that would have gone to a cloud service towards a running local instance of the emulator.

The framework in question is the one most people mean when they say serverless, a configuration file that describes functions, events and resources, and a command that deploys them. What the plugin changes is the destination of that deployment. Instead of the public cloud, the functions land in a container running on your machine, and the state they create, the buckets, the queues and the databases, is created locally too.

That sounds like a small change and it is not. The framework does not have a single place where it decides on a service endpoint; it has many, and it constructs clients all over the place. So the plugin's work is to intercept the client construction and point every one of those clients at the local address, which is why the configuration has both a global host property and a set of individual endpoint properties, and why the readme says the two may be mixed, so you can redirect everything globally and then override the one service you want to keep talking to for real.

There is one prerequisite that shapes the whole design, and it is stated in the readme rather than buried: the plugin requires the paid tier of the emulator, and the paid tier needs an authentication token in an environment variable. So this is not a free tool, and the readme is upfront about it. The reason is almost certainly the feature set described later, in particular the mounting of function code, which is a capability of the paid tier rather than of the open one.

Installation is two commands: the framework itself installed globally, and the plugin saved as a development dependency of the project. Saving it as a development dependency is the important half, because a plugin that is a runtime dependency of your deployed application would be deployed as well, and the point of this tool is that it is not there in production.

The project is MIT licensed, maintained by the emulator's own team, and the version in the manifest is a one point four, which with the deprecations the readme documents is a sign of a tool that has been adjusted over time rather than designed once.

## Configuration, and the three options that are on their way out

The configuration can live in the framework's own configuration file or in environment variables, and the readme says the environment variables take precedence over the file.

The file form is a block under the framework's custom section, and the readme's example is worth reading in full because it is a catalogue of what the plugin can do. There is a list of the stages in which the plugin should be enabled. There is the host to connect to and the port. There is a flag to start the emulator in a container on deployment, and a list of networks to attach to that container afterwards, which is the local equivalent of attaching a function to a virtual private network. There is the flag for mounting code, discussed below. There is a flag for running the container commands with elevated privileges, and a path to a compose file to use instead of the command line tool. And there is the image to use.

And then the sentence that governs all of it: a host or individual endpoints must be configured, or the plugin will be deactivated.

That is a good default. A plugin that has no configuration does nothing, which means a project that has the plugin in its development dependencies but no local configuration is not broken by it. The failure mode is silence rather than a crash, and for a tool that changes where your infrastructure is created, silence is the right failure mode.

The environment variables are the newer part of the story, and they carry a deprecation that says a lot about how the tool has been maintained. There is the token for the paid tier. There is a variable for the endpoint address, and the readme says it is the recommended configuration and that it replaces the deprecated options below. Then come the three deprecated ones: the edge port, the host name, and whether to use a secure connection. Each has a default, and each is marked deprecated in favour of the single endpoint variable.

That is a well-executed deprecation. The three older options were separate because they had to be: different clients took different pieces of the address, and a framework configuration file expressed them separately. A single endpoint variable is simpler, and it is the shape the wider ecosystem has since adopted. The readme is explicit that the old ones still work, so an existing configuration does not break, and it is explicit that they are on their way out.

The practical advice for a new user is the one the readme gives: use the single endpoint variable, and put the token in the environment rather than in the configuration file, because the token is a credential and the configuration file is usually in version control.

## Stage activation, and why the plugin stays out of production

There is one piece of configuration that deserves its own section, because it is the mechanism that makes the plugin safe to keep in a project at all.

The plugin activates if either of two things is true. Either the stage the framework is running is included in the list of stages you configured. Or the framework is invoked with no stage flag, in which case the default stage applies, and no stages list is configured at all.

So the default behaviour, with no configuration beyond a host, is that the plugin is active in development and inactive everywhere else. The stage list is the escape hatch for projects that deploy from a pipeline with a stage name of their own, or that want the local emulator in a stage other than the default one.

This is a design decision with more care in it than it looks. The alternative designs are worse in both directions. A plugin that is always on would break production deployments, because a deploy to the real cloud would create resources in the emulator instead, and the failure would be silent and total. A plugin that needs an explicit opt-in flag would be off by default, and a developer who has not read the documentation would deploy to the real cloud and be surprised.

Tying the activation to the stage name gets both properties at once. The stage is a thing the framework already knows about, it is already in the developer's command line, and it already differs between a laptop and a pipeline. So the plugin can be always-installed in the project and still be off in the place where being on would be a disaster.

The other thing the rule gets right is the second condition. If the framework is invoked with no stage flag and there is no stages list, the plugin is active. That is the case that catches a developer who runs the deploy command without thinking, which is the common case, and it means the local emulator is what you get by default.

And the readme's last section in the configuration part is a heading about enabling the plugin only for the listed stages, which is a title rather than prose, and the section text is cut off in the version available here. Which is a small piece of evidence about the documentation style of this project: some of the rules are in the headings, and some of the prose is in the wiki.

## Mounting the code, and the five steps you skip

The performance feature is the one that makes this plugin worth having, and the readme explains it by describing what happens when you do not use it.

Without mounting, the function code is deployed the traditional way: it is packaged, uploaded to the local object storage service, downloaded again by the local function service, extracted, and finally copied or mounted into a container to run. Five steps, each one a round trip through a service running on your own machine, for every function, on every deploy.

The alternative is to mount the project directory straight into the container that runs the function code. Then the deploy does not move the code at all, and the container reads the files from the mounted directory. The flag takes either a boolean, which mounts the project root, or a relative path, and the example given is the functions directory.

The readme is careful about the limits. Mounting code from several projects at once is not supported by the simple configuration, and the reason given is instructive: your code is mounted in the container at start up, so the container has to be started with the mount already in place. The fix is the autostart feature, so the plugin starts the container itself and the mount is there when it starts, or launching the emulator by hand with the volumes specified.

```sh
localstack start --docker -d \
  -v /path/to/project-a:/path/to/project-a \
  -v /path/to/project-b:/path/to/project-b
```

That command is the general form of the trick: the emulator is started with a volume for each project, so the container sees the same directories the host has, and the plugin's job reduces to telling it where to look.

The bundler integration is the other half of the performance story, and it is handled with a detection rule rather than a configuration option. If the project uses one of the well known bundler plugins for the framework, the plugin detects it and modifies the mount paths to point at the bundler's output directory rather than at the source directory.

The important caveat is that you have to invoke the build command yourself for the mounted code to update, because the mount points at the output, and the output only changes when the build runs. There is no watch mode out of the box, and the readme suggests a file watcher to provide one, with a project configuration that rebuilds on a source change and redeploys.

```json
  "scripts": {
    "build": "serverless webpack --stage local",
    "deploy": "serverless deploy --stage local",
    "watch": "nodemon -w src -e '.*' -x 'npm run build'",
    "start": "npm run deploy && npm run watch"
  }
```

And then the section about the bundler's own requirements, which are the kind of detail that saves an afternoon: the output directory has to be the conventional one, and the contents have to be retained, which means turning on the option to keep the output directory in the framework's own configuration. The reason for the retention requirement is the mount: if the bundler cleans the output directory, the mount points at an empty directory.

## The environment settings, and the two that are about the host

There is a short list of environment variables that the readme calls environment configurations, and two of them are worth explaining on their own.

One is for the mounting directory, and its example is a scenario that will be familiar to anybody who develops on one operating system and runs containers on another. Deploying a serverless application in a Linux virtual machine that runs containers, on a Windows host, where the flag for mounting a host directory into a container needs the host side of the path written in the host's own form. So the variable lets the user say what the working directory for the mounts is, and the example is a Windows-style path even though the containers are Linux.

That is a small setting for a narrow situation, and its presence tells you something about the audience. A developer on a single operating system never needs it, and the readme's example is a path with a drive letter in it, so the people who need it are the ones working across a boundary between the two.

The other two are about where the function code runs, and they are named after the emulator's own settings rather than the plugin's, with the readme pointing at the emulator's repository for the details. One is the executor type, which defaults to running in containers. The other says whether to assume the function containers are talking to a container engine on another machine, and it defaults to assuming they are not.

The third entry is the odd one and the most interesting. There is a variable holding a magic bucket name for the function mount and for hot reloading, and the name of the variable makes it clear that this is a convention between the plugin and the emulator: a bucket with a particular name marks the function code, and the emulator's tooling uses it to know what to reload.

That is a shared protocol between two projects, expressed as a name that both sides agree on, and it is the kind of coupling that is invisible in a plugin's own documentation and would be a nasty surprise if it changed. Which is why the variable exists: it is there so that somebody does not have to read both codebases to find out why it works.

The fact that these settings are environment variables rather than configuration file entries is consistent with the rest of the design. The token, the endpoint and the executor settings are all properties of the machine rather than of the project, and putting them in the environment keeps them out of a configuration file that gets committed.

## The repository, the tests and the container

The top level of the repository is small, and the test setup is the part that says the most about how the project is worked on.

There is a source directory, a specification directory, an example directory containing a service, a lint configuration, and the two files that come with a project that is also a container image. And there is a file that excludes files from the published package, which is the mechanism that keeps the example and the tests out of what a user installs.

The scripts in the manifest tell you the testing strategy, and there are three tiers. There is a unit test command, and a watch variant that runs the same thing. There is an integration test command with a different configuration file, and a watch variant of that one that uses a file watcher to restart on changes to the integration directory. And there is a coverage command.

Two configuration files rather than one is the detail worth noting, because the two suites are different in kind. A unit test for this plugin is a test of the configuration handling: given a configuration and a stage, which endpoints does the plugin decide on, and does the deprecation logic work. An integration test is a test that actually starts the emulator and deploys a function to it, which is slow, is prone to needing a container runtime, and is exactly the kind of test that is worth having and exactly the kind that is annoying to run on every save. So the fast suite is the default and the slow one is opt-in, and the watch variant of the slow one exists for the developer who is changing the plugin itself.

The dependency list is also telling. The archive library is there because the traditional deployment path involves zipping the function code, the cloud SDK is there because the plugin has to construct and redirect the service clients, and a promise promisifier is there to bridge the older callback style of the SDK into promises. The rest is tooling: an assertion library with a string-specific extension, the test framework, a YAML parser and a converter, a file system library, a watcher, and the framework itself as a development dependency so that the tests can run against it.

And then there are the two container files, which are the most interesting part of the repository for anybody thinking about how the tool is used.

The compose file starts the paid tier of the emulator, binds the gateway port and the range of ports the individual services use, and mounts two volumes: one for the emulator's own state and one for the host's container socket. The socket mount is the significant one, and it is the standard way a local tool manages containers: without it the emulator could not start the containers your functions run in. It also means the emulator's container has the host's container engine available, which is a large amount of authority, and a reader who is running this on a machine with anything else on it should know that.

The other container file is the plugin's own image, and its base is a Node version from many years ago. That is a piece of history rather than an accident of style: the file was written when that was a current version, and it has been left alone because the image is not what anybody uses any more. It is a good illustration of how a small project accumulates an archaeology of its own past.

## Conclusion

serverless-localstack fits a developer whose serverless work is a feedback loop rather than a deployment, and who can afford the paid tier of the emulator because that tier is what the plugin's features depend on. It does not fit somebody who wants to test against a faithful reimplementation of every cloud service. Verify first that the stages you have configured are the ones you develop in, since the plugin activates on stage names and is off otherwise, that the environment variable that names the endpoint is the current one rather than one of the deprecated ones, and that you understand what the container configuration mounts, because the local stack's compose file includes the host's container socket.

## FAQ

### What does the serverless-localstack plugin do?

It redirects the requests that a serverless framework would otherwise send to cloud services towards a local emulator, so that an application can be deployed and tested on your own machine. It supports a global host setting and individual endpoint overrides, which can be mixed, and it is deactivated if neither is configured.

### How does the plugin avoid affecting production deployments?

Its activation is tied to the framework's stage. It is active when the stage being run is in the configured list, or when the framework is invoked with no stage flag and no list is configured, which is the default development stage. So the plugin can stay in the project as a development dependency and still be off wherever being on would be a disaster.

### What is the mount code option in serverless-localstack?

It mounts the project directory, or a path within it, into the container that runs the function code, so that deploying does not package, upload, download, extract and copy the code every time. Without it the code follows the traditional path through the local object storage and function services, which is correct but slow. Mounting from several projects at once needs the container started with the volumes specified.

### Which configuration options are deprecated?

The separate port, host name and secure connection options are, and the readme says they are replaced by a single endpoint variable that it recommends instead. The older ones still work, so an existing configuration does not break, but the environment variable form is the one to use in a new setup.

### Does serverless-localstack work with a bundler?

Yes, and the integration is automatic: if the project uses one of the well known bundler plugins, the plugin detects it and points the mount at the bundler's output directory rather than the source. The caveat is that you have to run the build yourself for the mounted code to update, since the mount points at the output, and the bundler's own configuration has to keep its output directory and use the conventional output path.

## Sources

- [Issues](https://github.com/localstack/serverless-localstack/issues)
- [localstack/serverless-localstack on GitHub](https://github.com/localstack/serverless-localstack)
- [README](https://github.com/localstack/serverless-localstack/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/localstack-serverless-localstack
