Leveraging act's --reuse Flag for Docker Containers to Maintain State Between Runs
The --reuse (or -r) flag tells act to preserve Docker containers, volumes, and networks after successful workflow runs, enabling stateful local testing by conditionally skipping cleanup logic defined in the runner configuration.
The nektos/act CLI tool executes GitHub Actions workflows locally using Docker containers. By default, act creates fresh containers for each run and deletes them immediately after completion. Leveraging act's --reuse flag for Docker containers allows developers to maintain state between executions, significantly speeding up iterative development when working with databases or cached build artifacts.
How the --reuse Flag Works
The reuse functionality spans the CLI interface, runner configuration, and container lifecycle management. When enabled, the flag propagates through three architectural layers to suppress container removal operations.
CLI Flag Definition and Configuration
The flag is defined in [cmd/root.go](https://github.com/nektos/act/blob/master/cmd/root.go#L83-L84) using Cobra's boolean flag syntax:
rootCmd.Flags().BoolVarP(&input.reuseContainers, "reuse", "r", false,
"don't remove container(s) on successfully completed workflow(s) to maintain state between runs")
The value is stored in the Input struct and later copied into the runner configuration at [cmd/root.go line 613](https://github.com/nektos/act/blob/master/cmd/root.go#L613):
ReuseContainers: input.reuseContainers,
Runner Configuration Structure
The flag ultimately resides in the Config struct defined in [pkg/runner/runner.go lines 31-32](https://github.com/nektos/act/blob/master/pkg/runner/runner.go#L31-L32):
type Config struct {
// …
ReuseContainers bool // reuse containers to maintain state
// …
}
This boolean field drives all conditional cleanup logic throughout the execution pipeline.
Container Lifecycle Modifications
When ReuseContainers is set to true, act modifies the cleanup behavior for three distinct container types: job containers, step containers, and service containers.
Job Container Preservation
In [pkg/runner/run_context.go lines 55-63](https://github.com/nektos/act/blob/master/pkg/runner/run_context.go#L55-L63), the cleanup closure checks the configuration before removing the job container and its associated volumes:
reuseJobContainer := func(_ context.Context) bool {
return rc.Config.ReuseContainers
}
...
return rc.JobContainer.Remove().IfNot(reuseJobContainer)
.Then(container.NewDockerVolumeRemoveExecutor(rc.jobContainerName(), false)).IfNot(reuseJobContainer)
.Then(container.NewDockerVolumeRemoveExecutor(rc.jobContainerName()+"-env", false)).IfNot(reuseJobContainer)
When ReuseContainers is true, the IfNot(reuseJobContainer) condition disables removal calls. This leaves the job container intact along with its named volumes (<job-name> and <job-name>-env).
Step Container Preservation
Individual step containers follow the same conditional pattern. In [pkg/runner/step_docker.go lines 79-84](https://github.com/nektos/act/blob/master/pkg/runner/step_docker.go#L79-L84):
stepContainer.Remove().IfBool(!rc.Config.ReuseContainers)
Similarly, composite actions that spawn their own containers use identical logic in [pkg/runner/action.go lines 350-354](https://github.com/nektos/act/blob/master/pkg/runner/action.go#L350-L354):
stepContainer.Remove().IfBool(!rc.Config.ReuseContainers)
If ReuseContainers evaluates to true, the IfBool condition prevents container removal.
Service Containers and Networks
Service containers defined in the services: block of your workflow (such as MySQL or Redis) are governed by the same reuseJobContainer flag. The network cleanup code in run_context.go also respects the flag, ensuring that the Docker network connecting job and service containers persists between runs when reuse is enabled.
Practical Usage Examples
Basic Reuse Workflow
Start by invoking act with the short or long flag form:
# First run creates containers and preserves them
act -r
# Subsequent runs reuse existing containers
act --reuse
Stateful Database Testing
Consider a workflow that initializes a PostgreSQL database. Without reuse, each run recreates the database from scratch. With --reuse, the database state persists:
# .github/workflows/ci.yml
name: CI
on: [push]
jobs:
test:
runs-on: ubuntu-latest
services:
db:
image: postgres:15
env:
POSTGRES_PASSWORD: password
ports: ["5432:5432"]
steps:
- uses: actions/checkout@v4
- name: Seed database
run: |
psql -h localhost -U postgres -c "CREATE TABLE test (id serial);"
Execute with reuse to maintain the seeded data:
# First run initializes PostgreSQL
act -r
# Second run connects to the existing container with previous state intact
act -r
Inspect the preserved container using standard Docker commands:
docker ps -a | grep act-
docker exec -it <container-name> bash
Manual Cleanup Procedures
When you need to remove containers that were preserved with --reuse, use Docker's CLI:
# Remove specific job containers
docker rm -f $(docker ps -aq -f name=<workflow-job-name>)
# Remove associated volumes
docker volume rm $(docker volume ls -q -f name=<workflow-job-name>)
# Or run act without --reuse to trigger automatic cleanup on the next execution
act
Summary
- The
--reuseflag maps toConfig.ReuseContainersin the runner configuration struct. - Job containers, step containers, and service containers are all preserved when the flag is enabled.
- The flag only affects successful workflow runs; failed runs still trigger container cleanup unless combined with other flags.
- Associated Docker volumes (named
<job-name>and<job-name>-env) remain intact between executions. - Manual cleanup requires explicit
docker rmanddocker volume rmcommands targeting the preserved resources.
Frequently Asked Questions
Does the --reuse flag prevent cleanup when a workflow fails?
No. According to the implementation in cmd/root.go, the --reuse flag specifically applies to "successfully completed workflow(s)." If your workflow fails, act removes the containers regardless of the --reuse setting. To force cleanup after failures, you would use the --rm flag instead.
Can I access files written inside reused containers on my host machine?
No. The --reuse flag preserves the container's filesystem state in Docker's storage layer, but it does not mount or sync those files to your host filesystem. Files written inside the container remain inside the container's writable layer. To extract artifacts, use docker cp to copy files from the preserved container to your host.
Are service containers defined in the workflow YAML also reused?
Yes. Service containers (such as databases or cache services defined under the services: key) are governed by the same reuseJobContainer logic in run_context.go. When --reuse is enabled, these containers persist alongside the main job container, maintaining their state (including database tables, cached data, and configuration) between workflow runs.
How do I clean up containers when I'm done using --reuse?
You have two options for cleanup. First, run act without the --reuse flag on your next successful execution, which will remove containers created in that specific run. Second, manually remove containers using docker rm -f and volumes using docker volume rm, targeting resources by the job name prefix (e.g., act-<job-name>). Docker's filtering flags (-f name=act-) help identify act-managed resources.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →