How to migrate from Docker Machine runners before GitLab 20.0
If your CI fleet runs on Docker Machine, you need to move it before GitLab removes that executor in GitLab 20.0, which the maintenance policy plans for May 2027. The work is a config mapping, a pilot and a tag-based cutover, and its size depends on how many runner pools, cache setups and deploy paths your fleet has. This is an infrastructure change with a fixed deadline, and it leaves pipeline code alone when jobs already select runners by tag, so the .gitlab-ci.yml files should need little or no change. Confirm the exact removal behavior and any deprecation warnings against the current GitLab Runner documentation before you set internal dates.
Start with an inventory of every config.toml on your runner managers. For each [[runners]] block, record the executor, the machine and cloud settings, the idle and maximum counts, the autoscaling schedule, concurrency, tags and any cache configuration. Each Docker Machine setting then maps to one of two targets: the Docker Autoscaler executor, where a fleeting plugin manages a pool of cloud instances, or the Instance executor, where jobs need the whole host. The provisioning model changes, so each setting needs a named equivalent or a documented reason it no longer applies. Your platform team or a partner working under a written scope pays for this analysis and the pilot. The recurring costs are the runner VMs, the manager host and cache storage, and they keep running after the cutover.
Run the new fleet beside the old one. Register new runners with new tags, pilot them on low-risk projects, then move projects tag by tag, leaving deployment jobs for last and keeping them on protected-branch runners. Watch for caches that existed only on the old machines, secrets scoped to the old runner registrations, and runner versions that must match your GitLab version, since the documentation advises keeping Runner major and minor versions aligned with GitLab. Keep the Docker Machine managers online until they have no active jobs, then remove them. Before you request a quote, gather the list of runner pools, pipeline volume per pool, cache usage and each deploy path, because those four items drive most of the effort. Confirm the mapping for each setting in the GitLab Runner documentation.
Which Docker Machine config.toml settings map cleanly to Docker Autoscaler
This table compares common Docker Machine settings in config.toml with their Docker Autoscaler or Instance executor equivalents, and flags the ones whose meaning changes. Read each row as a starting point: confirm every key against the Runner configuration reference for the version you are running, because several settings move into a different section or change how capacity is held.
| Docker Machine setting | Docker Autoscaler or Instance equivalent | Migration note |
|---|---|---|
| Machine options (driver, cloud size, image, network) | Fleeting plugin settings and the instance template or group configuration in the autoscaler section | Changes meaning: instances are provisioned by a fleeting plugin for your cloud, so driver-specific options must be rewritten for that plugin. Confirm the plugin's own options. |
| IdleCount | Autoscaler idle count and idle scale settings | Changes meaning: idle capacity is held by the autoscaler against the fleet, not by a Docker Machine pool. Test how many idle instances you actually keep warm and what that costs. |
| IdleTime | Autoscaler idle time setting | Closest match, but confirm units and the point at which idle instances are removed in your version. |
| MaxBuilds | Max use count or equivalent limit on jobs per instance | Not always one-to-one. Confirm whether your version limits builds per instance and how that interacts with instance replacement. |
| Capacity per machine (limit) | Capacity per instance under the autoscaler section | Keep the value in step with instance size and your job concurrency. Do not assume the Docker Machine value carries over unchanged. |
| Cache volumes and cache location | Docker volumes plus distributed cache settings, typically in object storage | Changes meaning: fresh instances start empty, so a local cache on the old machine does not carry over. Plan a shared cache, such as the S3-compatible cache, before cutover. |
| Privileged mode | Privileged setting in the Docker executor section of the Runner config | Keep the same intent, but review it. Privileged jobs on autoscaled instances should run only on runners limited to trusted projects. |
| Registry mirrors and pull settings | Docker registry and pull policy settings, plus any mirror configured on the instance image | Confirm how the image and registry credentials reach new instances, since they no longer come from a pre-built Docker Machine host. |
| Runner tags and executor type | Same tags on the new runner, with executor set to Docker Autoscaler or Instance | Tags are the basis of a parallel cutover. Register new runners with separate tags first, then move jobs over once they pass. |
Documented paths for the runner move
The runner move is a reconfiguration and re-registration exercise, not an import. Each card below names a documented piece the team will work with, so the cutover can be planned against published behavior rather than memory of the old setup. Use them together, in the order the migration plan needs them.
Executor and config.toml reference
The GitLab Runner documentation defines each executor and the config.toml keys that control it. Map every [runners.machine] setting in your current file to its equivalent under [runners.docker.autoscaler] or the Instance executor settings, and mark any key with no clear match as a decision for the team. Confirm each key against the reference for the Runner version you will deploy, since names and defaults change between releases.
Current runner registration flow
New runners are registered with the authentication flow GitLab documents for the release you run, not by reusing old registration commands. Confirm which token type your instance accepts, how automation such as configuration management or the Helm chart supplies that token, and where the token is stored. Check this before the first new runner is registered, because the old workflow can be disabled on a different schedule than your cutover.
Fleeting plugins for instance groups
Docker Autoscaler and the Instance executor provision cloud VMs through fleeting plugins, which manage instance groups on the target cloud. Confirm which plugin your provider supports, what permissions the runner manager needs to create and delete instances, and how the plugin reports capacity. Plan the instance group as its own resource with its own tags, so it can be tested in isolation before any production job reaches it.
Runner tags for job routing
Tags are how a job asks for a particular runner, so they are the mechanism for a parallel cutover. Run the new fleet under a tag set that no production job uses yet, then move jobs in small groups by changing the tags keyword in their pipeline definitions. Keep a written list of which projects and branches moved on which date, so a rollback is a tag change rather than a search through pipelines.
Runner and GitLab version alignment
GitLab's Runner guidance is to keep the Runner major and minor versions aligned with the GitLab instance. Before you choose the Runner version for the new fleet, check it against the GitLab version you run, and record the pairing in the change plan. Because GitLab's major release and upgrade stops also affect Runner support, schedule any GitLab upgrade and Runner upgrade in the same change window rather than leaving them to drift.
Backup of existing runner configuration
Before any change, copy the current runner configuration files, the registered runner list, and the Docker Machine provisioning settings, and store them outside the runner host. Keep the old fleet running until every project has moved and a rollback test has passed, so the backup is a working fallback and not only a record. Note that the backup covers the runner configuration, not the instance backup procedure, which is a separate task.
Sequence from inventory to decommissioning the Docker Machine pool
This sequence moves CI jobs from Docker Machine runners to a Docker Autoscaler pool in controlled stages, so each tag group is proven before the old machines are retired. Each gate has a named check, and the old pool is removed only after the new one has carried real work.
Inventory every runner and its dependencies
The platform or runner owner records each runner's config.toml section, registered tags, executor and autoscaling settings, cache configuration and the projects or groups that rely on it. Include runners registered at instance, group and project level, since a missing entry is the usual reason a project breaks after cutover. The step is done when every Docker Machine runner has a row in an inventory that lists its tags and dependent projects, and someone has confirmed that list against the GitLab runner pages in the admin area.
Choose the target executor per workload
For each tag group, decide whether Docker Autoscaler fits the job pattern or whether the workload needs the Instance executor because it requires the whole host, operating system or attached devices. Record the reason next to each group, along with any settings that do not map directly from the old machine options. Confirm the decision with the teams that own the workloads. The step closes when each tag group has a named target executor and a short list of settings that need review.
Build the Docker Autoscaler pool on a pilot tag
Create the new autoscaling pool on a tag that no production pipeline uses, mapping config.toml settings to the Docker Autoscaler configuration and confirming the fleeting plugin, machine capacity, cache location and cleanup behavior. Run a pilot project that exercises builds, services and the cache. Check job success, cache restores on fresh instances and whether idle instances are removed after the configured idle period. The step is done when the pilot runs repeatably and the team can explain every setting it changed from the original.
Run both pools in parallel by tag group
Keep the Docker Machine runners active and register the new pool under its own tags. Move one tag group at a time by updating the
.gitlab-ci.ymltags for a small set of projects, starting with low-risk workloads. Watch queue times, failed jobs and runner logs for each group before moving the next one. Record cache hit behavior and any jobs that fail on the new executor. The step is done when each moved group has run for a period the team judges sufficient and shows no unexplained failures.Cut over remaining tags after queue and failure checks
Move the rest of the tag groups to the Docker Autoscaler pool, verifying at each batch that queue lengths are acceptable, that instances scale up and back down, and that job logs show clean cleanup between jobs. Confirm that protected-branch and deployment jobs still reach the runners they are meant to use. Stop the cutover if failure patterns appear and return the affected group to its previous tags. The step is done when no project still points at the Docker Machine tags and the inventory shows zero active dependencies.
Decommission the old machines and remove credentials
Once no jobs are routed to the old runners, stop the Docker Machine runner services, remove their registrations from GitLab, and delete the stored credentials, cloud access keys and any machine-level secrets the old pool used. Revoke access that existed only for that pool and update the runner inventory and monitoring. Ask your team to confirm the list of retired credentials against the cloud provider's access records. The step is done when the old runners no longer appear in the GitLab interface, no cloud resources remain for them and the credential removal is recorded.
Choosing Docker Autoscaler or the Instance executor for each pool
Most build and test jobs compile code, run unit tests and package artifacts inside a container image. Docker Autoscaler starts a Docker engine on each cloud VM or instance group member, and the runner creates a container for each job. Jobs share the host kernel, but each job's dependencies stay separate from the next job's.
- Isolation boundary: Jobs are isolated at the container level and share the host kernel. Confirm that your trust model accepts this boundary for every project placed in the pool.
- Privileged work: Jobs that build images or request privileged mode widen the boundary. Confirm whether those jobs belong in a separate pool with tighter access.
- Fresh-VM caching: New VMs start without local state, so dependency restore depends on a shared cache. Confirm the cache location and access rules before cutover.
- Operating model: Your team maintains the VM image with Docker, the runner manager and the fleeting plugin configuration. Confirm who patches the image and on what schedule.
Check whether these jobs need only a container image, and whether your trust model accepts shared-kernel isolation for them.
Some jobs need the operating system itself: kernel modules, attached hardware such as test rigs or hardware tokens, nested virtualization, or system services that must persist across steps. The Instance executor runs each job directly on an autoscaled VM instead of inside a container, so the job sees the full host. The GitLab Runner documentation covers the executor settings in detail.
- Per-job separation: Each job runs on a fresh instance, so host-level changes do not carry into later jobs. Confirm how the instance lifecycle and removal behave for your fleet.
- Device and OS access: Attached devices and host packages are available without container passthrough. Confirm the exact devices, drivers and kernel versions on your image.
- Cost per job: An instance is occupied for the full length of each job, which can cost more for short jobs. Confirm instance size and boot time against your actual pipeline mix.
- Image maintenance: The image carries the full OS toolchain, so patching and configuration drift take more effort. Assign a named owner before the pool goes live.
List the jobs that require the host, and test each one inside a container before moving it to this pool.
Many organizations run both kinds of work. Splitting the fleet into pools by workload lets each pool have its own image, scaling limits and access rules. Jobs select a pool through runner tags, so placement is explicit. Trust level and workload should drive the split first, with convenience second.
- Pool per workload type: Keep Docker Autoscaler and Instance pools under distinct tags so a job cannot land on the wrong executor by accident.
- Access by pool: Deployment pools usually need tighter secret access than build pools. Confirm which secrets and environments each pool can reach.
- Separate capacity settings: Each pool has its own idle capacity and scaling limits, so both need sizing. Confirm the expected job mix for each pool first.
- Sequenced moves: Migrate one workload class at a time and keep the old runner registered until the new tags pass. Confirm the rollback path for each step.
Write down which jobs go to each pool, and name the people who approve changes to those tag assignments.
What drives the effort and duration of the migration
This table lists the factors that change how much work the move from Docker Machine to Docker Autoscaler takes, the direction each factor pushes the effort, and who typically carries the cost. Read it before requesting a quote, and use the last column to see what to put in writing. No durations or prices are given because the answer depends on your fleet.
| Driver | Effect on effort | Who pays | What to ask for in a quote |
|---|---|---|---|
| Number of runner pools and config.toml sections | More pools mean more sections to map, test and cut over. Effort rises with each pool. | Internal staff review the inventory. Partner converts and tests each pool. | A per-pool mapping of config.toml sections to Docker Autoscaler or Instance executor settings. |
| Projects and tags tied to each pool | Every tag a pool serves needs a test job and a cutover step. Shared tags raise coordination work. | Internal staff own the project owners and sign-off. Partner builds the tag test matrix. | A list of tags and projects per pool, and the cutover order for each. |
| Privileged mode | Privileged containers need a separate security review and may change the executor choice. Effort rises when many jobs depend on it. | Internal security team approves the risk. Partner documents the settings. | A written review of which pools need privileged mode and the isolation plan for each. |
| Custom volumes and host-level dependencies | Jobs that mount host paths, devices or preinstalled tools must be rebuilt or moved to images. This is often the largest unknown. | Internal teams fix their own pipelines. Partner reproduces host dependencies in the new fleet. | An inventory of host paths, devices and installed packages, with an owner for each. |
| Cache design | A shared cache in object storage must be configured and tested before cutover. A local cache on fresh instances can slow jobs and surprise teams. | Cloud provider bills storage. Internal staff or partner configures the cache. | The cache location, retention policy, access rules and a test that a job restores dependencies on a new instance. |
| Cloud and network placement of the manager | The manager must reach GitLab and the cloud API. Firewall, proxy and private endpoint rules add review cycles and test time. | Internal network team approves changes. Cloud provider charges for network resources. | A network diagram, the required outbound endpoints and the team that approves firewall changes. |
| Deployment runners tied to protected branches | Deploy jobs need the same protection rules after the move. Errors here affect production, so validation takes longer. | Internal release owners verify. Partner tests protected-branch behavior. | A test plan showing each protected branch and environment runs on the expected runner. |
| Change-control and approval cadence | Each change window adds scheduling time. Fewer, larger windows make the parallel cutover slower to finish. | Internal change board and service owners. Partner prepares change records. | The change calendar, approval steps and the rollback procedure for each window. |
| Staffing familiarity with the cloud | Teams new to autoscaling groups, instance templates and cloud IAM need more time to learn and verify. Familiar teams move faster. | Internal staff carry the learning cost. Partner adds capacity if skills are missing. | Named engineers on each side, and whether knowledge transfer and runbooks are included. |
What usually goes wrong during a runner executor migration
Most failed runner migrations fail in predictable ways: a rushed cutover, a version mismatch, lost caches, weaker isolation, leftover credentials and jobs that no longer match a runner. Each card below names one risk, the step that prevents it and what to check before moving the next team.
Flag-day cutover strands jobs
Switching every project from Docker Machine to Docker Autoscaler in one change leaves in-flight and queued jobs with no runner that accepts them. Keep both pools registered at once and move one tag group at a time, starting with low-risk projects. Confirm that the old pool still drains its running jobs before you retire any machine. Run both pools in parallel until each tag group has passed a full working week of jobs.
Runner version out of step
A new Docker Autoscaler runner paired with an older or newer GitLab can behave differently from the fleet you tested. The GitLab Runner documentation advises keeping Runner major and minor versions aligned with GitLab, so check the Runner version against your instance before registering the new pool. Record the versions in your change ticket so an auditor or on-call engineer can see what ran each job.
Cache lost on fresh instances
Docker Machine hosts often kept dependency caches that autoscaled Docker Autoscaler instances do not have, so builds that relied on warm caches slow down or fail to restore packages. Decide where the shared cache lives before cutover, typically in object storage, and test a cold job on a brand new instance. Measure job duration on the new pool against the old one so you know whether the cache design holds.
Deployment jobs on weaker isolation
Moving a deployment job from a dedicated, protected Docker Machine pool to a shared autoscaled pool can expose production credentials to untrusted code from other projects. Review which jobs need protected-branch access and keep those jobs on runners that only serve protected refs. Confirm the executor and tag setup isolate privileged jobs before any deployment pipeline points at the new fleet.
Credentials active on the retired pool
Runner tokens and cloud credentials attached to the retired Docker Machine manager stay valid after its machines are deleted, so anyone who still holds them can keep registering or pulling jobs. Once a tag group has moved, revoke the old runner tokens and remove the cloud access keys the manager used. Record each revocation so the migration can be audited later.
Missing tags leave jobs queued
A job tagged for a runner type that no longer exists will sit in the queue without an error that points to the cause. Map every existing tag in your pipelines to the new pool before you disable the old one, and watch queue time per tag during cutover. Alert on jobs waiting longer than your usual baseline so a missing tag shows up within the first day rather than in a release week.
Questions teams ask before moving off Docker Machine runners
How much does migrating off Docker Machine runners cost?
The cost depends on the size and shape of your runner fleet, the number of pipelines that depend on specific runner tags, the cloud accounts involved and how much testing your pipeline owners need before sign-off. The main components are planning and inventory, configuration mapping from config.toml to the new executor, building the autoscaling infrastructure in your cloud accounts, running the parallel cutover and handing over runbooks. Most of this is one-time work. Recurring costs sit mainly with your cloud provider for runner capacity and with your own team for ongoing maintenance and version alignment. To get a quote, ask for a written scope that lists the runner count, executors, cloud targets, tag routing, and the deliverables and sign-off points. DeployOpen agrees scope and deliverables in writing before work starts.
How long does a Docker Machine to Docker Autoscaler migration take?
Duration is driven by inputs rather than a fixed period. The biggest factors are how many runners and runner groups you operate, how many projects and teams route jobs by tag, how complex your existing autoscaling settings are, and how many cloud accounts or regions are involved. Change windows and the approval process for each team also set the pace, since cutover happens tag by tag while both fleets run. Confirm your removal deadline early so the plan works backward from it. Ask any partner for a timeline that lists the phases, the dependencies between them and the decisions your team must make at each step.
Can our own team migrate the runners without a partner?
Yes, if your team already has working knowledge of GitLab Runner configuration, your cloud provider's compute and networking services, and the autoscaling plugins used by the Docker Autoscaler executor. You also need enough time to finish before the removal deadline and an owner who can coordinate with pipeline teams. Before deciding, confirm which executor you will use, check the current GitLab Runner documentation for supported setups, and test a parallel runner on a non-production project. If one of those skills is missing or the calendar is tight, a fixed-scope engagement can cover the gap while your team keeps ownership of the fleet.
What does the migration need from our team?
Expect to provide access and time from several groups. Platform engineers need read access to the GitLab instance and runner configuration files so the inventory can be built. Cloud administrators need to grant access to the accounts or subscriptions where new runner capacity will run, along with network and identity details. Pipeline owners must review their jobs, confirm tags and sign off on each cutover. Your change management team should agree on maintenance windows and rollback steps. Ask the partner to list each of these inputs, the named contact for each, and when each is needed in the schedule.
Should we choose the Instance executor or Docker Autoscaler?
The right choice depends on whether your jobs need the full host or can run inside containers on shared VMs. Docker Autoscaler suits jobs that run in containers and can share VMs with isolation provided by Docker. The Instance executor suits jobs that need the whole VM, its operating system or attached devices. The comparison covers isolation, cache handling, cost of idle capacity and operational effort. The tabs section on this page sets out the mapping in more detail, and you should test your heaviest pipelines on both before deciding.
What happens if we miss the Docker Machine removal deadline?
Docker Machine is deprecated, and removal is planned for GitLab 20.0. GitLab 20.0 is planned for May 2027, according to the GitLab maintenance policy. Confirm the current removal notice in the GitLab Runner documentation before you set your plan, because dates and wording can change. If removal lands before your fleet has moved, jobs that depend on those runners will stop running on upgrade. Plan a parallel cutover by runner tags so you can move pipelines in stages and keep a rollback path until each group has passed its checks.
Can DeployOpen run the runner cutover for us?
DeployOpen plans, implements, configures and documents self-hosted open-source platforms inside your own infrastructure, including GitLab Runner fleets in your cloud accounts. Most engagements are fixed-scope, so the scope, deliverables and timelines are agreed in writing before work starts. The scope would typically cover the inventory, the executor mapping, the autoscaling build, the parallel cutover by tag, and handover documentation. Ongoing support and upgrades can be added as a retainer. DeployOpen does not publish a price list, so a quote follows from the written scope.
How DeployOpen scopes, delivers and supports the runner migration
DeployOpen starts with an inventory of your existing Docker Machine runners, their config.toml settings and the jobs they serve, then agrees a written scope before any change is made. The migration is built as a parallel fleet on Docker Autoscaler or the Instance executor, cut over team by team through runner tags, with each step documented and handed over.
How we scope
- Inventory every Docker Machine runner, its config.toml settings, registered scopes and the tags jobs depend on
- Map each setting to its Docker Autoscaler or Instance executor equivalent and list the settings with no direct match
- Agree which teams, projects and tags move in each cutover wave, written into the fixed-scope agreement
- Confirm the GitLab and Runner versions in use so the target Runner stays aligned with your GitLab release
How we build
- Stand up the new autoscaling fleet alongside the existing Docker Machine runners so both can take jobs during cutover
- Configure cloud or hypervisor credentials, instance templates, idle capacity and cache settings to match the agreed design
- Test representative pipelines on the new runners, including jobs that rely on Docker-in-Docker, privileged settings or caches
- Record configuration as code and document the tag map, rollback steps and any settings that changed behavior
How we hand over
- Hand over runbooks for adding teams, rotating runner credentials and removing old Docker Machine runners
- Walk your platform engineers through the cutover log, test results and the decisions behind each mapped setting
- Confirm with you that no pipelines still depend on Docker Machine tags before retiring the old fleet
- Deliver a clear list of open items, such as settings to confirm with your cloud provider or security team
How we support
- Ongoing support, upgrades and runner version alignment are available as a retainer after handover
- Support can cover monitoring of the autoscaling fleet, changes to instance templates and responses to failed jobs
- Retainer scope, response expectations and fees are agreed in writing and quoted from your environment
- Before any future GitLab upgrade, confirm the Runner version and executor compatibility you need with your team

