Argo CD sync waves and resource hooks for ordered deployments

Argo CD sync waves and hooks let you order a rollout: migrations before the new version, smoke tests after it, and cleanup when a sync fails. This page explains how they work, what to configure and what to confirm first.

On this page

How Argo CD sync waves and resource hooks order a deployment

Sync phases, waves and hooks let Argo CD apply resources in a defined order during a sync, so a database migration can finish before new pods start and a smoke test can run only after the new version is in place. Phases group resources by when they run: PreSync, Sync, PostSync and SyncFail, with Skip for resources Argo CD should not apply. Waves order resources inside a phase, using the argocd.argoproj.io/sync-wave annotation. Resources default to wave 0, waves may be negative, and Argo CD applies them from lowest to highest. PreDelete and PostDelete also exist, but they run only when the whole Application is deleted, and they need Argo CD v2.10 or later.

Teams need these controls when a release has steps that Kubernetes does not order on its own: schema changes that must complete before the new Deployment rolls out, a verification job that should run only after a healthy sync, or cleanup that should happen only when a sync fails. If the workload is a single Deployment with no ordering dependency, plain Kubernetes rollout behavior is usually enough, and adding phases and waves adds review and debugging work. Before you rely on waves, confirm the default wait between waves in your Argo CD version, which the documentation sets through the ARGOCD_SYNC_WAVE_DELAY environment variable, and decide whether that delay is acceptable for your largest rollouts.

Hooks are ordinary Kubernetes resources, usually Jobs, annotated with argocd.argoproj.io/hook set to a phase. Each hook also needs a argocd.argoproj.io/hook-delete-policy, because without one, the default BeforeHookCreation policy keeps the previous Job until a new one replaces it, and failed or successful Jobs can accumulate across releases. Decide per hook whether to delete on success, on failure or before the next creation, and test a failed PreSync migration in a non-production cluster so you know what state the database and the old pods are left in. Confirm the exact annotation names and policy behavior against the sync phases and waves guide for the Argo CD version you run.

Which phase, wave and deletion policy fits each job

This table compares the hook phases, the sync-wave annotation, the wave delay setting and the hook deletion policies, with the job each one fits and its main trade-off. Read each row as a decision: pick the phase that matches when the work must run, then the policy that matches when its leftover objects should be removed.

OptionWhen it fitsMain trade-off
PreSync hookDatabase migrations or schema changes that must finish before the new manifests are appliedA failed PreSync job stops the sync, so the migration must be safe to retry
Sync hookWork that runs alongside the main resource apply during the syncIt gives no ordering guarantee against the resources it sits beside, so use waves when order matters
PostSync hookSmoke tests or cache warming that should run only after the new resources are healthyFailures mark the sync as failed, and the release may already be live when the test runs
SyncFail hookCleanup or alerting when a sync fails, such as removing partial resources or notifying a teamIt runs only on failure, so it cannot replace work that must always happen
Skip hookResources that Argo CD should track in Git but never apply during a syncSkipped resources are not created by the sync, so someone must create them some other way
PreDelete and PostDelete hooksTeardown tasks such as archiving data when a whole Application is deletedThey run only on full Application deletion (PostDelete needs v2.10 or later), not on routine resource removal
sync-wave annotation (default 0, negative values allowed)Ordering dependent resources, such as a namespace before its workloads, or a CRD before its custom resourcesWaves are applied from lowest to highest, so a wrong number can block a rollout; keep the number plan documented
ARGOCD_SYNC_WAVE_DELAY (2 seconds by default)Giving controllers time to reconcile between waves, for example a database operator before a dependent appA longer delay slows every sync; confirm the value in your own environment before changing it
HookSucceeded deletion policyRemoving a hook Job after it succeeds so completed runs do not accumulateA failed run stays in the cluster, which helps debugging but needs a separate cleanup plan
HookFailed deletion policyRemoving a hook only after it fails, keeping successful evidence in the clusterSuccessful jobs pile up over time unless another policy or retention step removes them
BeforeHookCreation policy (default)Replacing the previous hook run with a new one on the next sync, so names do not collidePrevious run logs are removed when the next sync starts, so capture logs before then

Patterns for migrations, smoke tests and cleanup

A PreSync hook runs a Job before Argo CD applies the rest of the sync, which makes it the usual place for a database migration that must finish before a new Deployment starts. Argo CD waits for the Job to complete successfully before it moves on to the Sync phase, so a failed migration stops the rollout before new pods replace old ones. That protects the running version only if the migration is written to tolerate a retry.

  • Make the migration idempotent: Argo CD may run the hook again after a failure or a retried sync, so each step should check whether it already happened.
  • Keep backward compatibility: Old pods may still be serving traffic while the migration runs, so columns should be added before code depends on them and dropped only in a later release.
  • Set a deletion policy: Use BeforeHookCreation or HookSucceeded on the Job so completed migrations do not accumulate in the namespace.
  • Plan for long migrations: Confirm with your database team how long the largest migration may run and whether it needs a lock-safe approach.
  • Keep credentials out of the manifest: Reference the database secret from the destination cluster rather than writing it into the Job spec.

Before you rely on this pattern, test a failed migration in a non-production cluster and confirm that the old version keeps serving traffic.

Setting up ordered syncs step by step

This sequence moves a deployment from an unordered sync to one where Argo CD applies dependencies first, runs database migrations and smoke tests at the right moments, and cleans up hook Jobs so they do not accumulate. Each step ends with a check you can run before the next one begins.

  1. Inventory dependencies between resources

    Start by listing every resource in the Application and noting which ones must exist or be ready before others apply. Typical examples are a Namespace before workloads, a ConfigMap or Secret before a Deployment that reads it, a database migration Job before a new application version starts, and a smoke test after the Deployment is healthy. Platform engineers and application owners should do this together, because they know the runtime order. The output is a short written dependency list. Check it by confirming that every resource appears once and that each dependency has a reason you can explain in one sentence.

  2. Assign sync waves to resources

    Add the argocd.argoproj.io/sync-wave annotation to each resource that needs ordering. Resources without the annotation use wave 0, and negative values are allowed, so you can place foundational objects in an earlier wave without renumbering everything else. Waves run from lowest to highest, and Argo CD prunes in reverse order. Keep the numbers sparse enough that you can add a wave later without editing unrelated manifests. Check the result by rendering the manifests locally and listing each resource with its wave, then confirming the order matches your dependency list.

  3. Assign hook phases to Jobs and tests

    Mark database migrations with the PreSync hook so they run before the main sync, and mark smoke tests with PostSync so they run only after the application resources are applied. Use SyncFail for cleanup or notification Jobs that should run when a sync fails. Hooks are Kubernetes resources with their own annotations, so keep each hook Job idempotent, because a retried sync may run it again. Confirm the phases by running a dry sync and checking that the migration Job is listed before the workload changes and the smoke test is listed after them.

  4. Set deletion policies so finished Jobs do not accumulate

    Add the argocd.argoproj.io/hook-delete-policy annotation to each hook. HookSucceeded removes the Job after it succeeds, HookFailed removes it after a failure, and BeforeHookCreation removes the previous Job before creating a new one. BeforeHookCreation is the default when no policy is set, so decide explicitly whether you want failed Jobs kept for investigation. A common approach is to pair HookSucceeded with a policy that retains failed Jobs long enough to read their logs. Check the result by running two syncs in a non-production cluster and confirming that only the Jobs you expect remain.

  5. Confirm the wave delay is the right default

    Argo CD waits 2 seconds between waves by default, controlled by the ARGOCD_SYNC_WAVE_DELAY setting. Leave it unchanged unless a dependency needs more time to settle, such as an operator that must register CRDs before custom resources apply. Changing this value affects every Application that uses the controller, so the platform team should own the decision and record the reason. Check it by reviewing the sync timeline in the Argo CD UI for a wave that waits on a readiness condition, and by confirming that a later wave does not start before the earlier one is healthy.

  6. Test the ordering in a non-production cluster

    Run the full sequence against a non-production cluster that matches your production layout as closely as you can, including the same Argo CD version, sync options and database engine. Trigger a normal sync, a sync that fails in a PreSync Job, and a sync that fails during a PostSync smoke test, then confirm that each failure behaves as the hook policies predict. Review the hook Job status, its logs and whether cleanup happened. Confirm the Argo CD version you test on is one that receives patch releases, since the project supports only the three most recent minor versions. Record the results so the production change has evidence to point to.

Why hook Jobs pile up and how deletion policies stop it

Hook resources such as Jobs persist in the cluster after they finish unless a deletion policy tells Argo CD to remove them, so the first decision is which policy each hook gets. Argo CD's hook deletion policies are HookSucceeded, HookFailed and BeforeHookCreation, set with the argocd.argoproj.io/hook-delete-policy annotation. HookSucceeded deletes the hook as soon as it succeeds, HookFailed deletes it only after a failure, and BeforeHookCreation deletes the previous hook object just before a new one of the same name is created in the next sync. When no policy is set, BeforeHookCreation is the default, which means a finished hook Job stays visible until the next sync replaces it. Confirm this default against the Argo CD version you run, since the sync phases and waves guide is the reference for the current behavior.

The trade-off is between debugging and clutter. Keeping a failed or completed hook Job lets your team read its logs and pod events after the sync finishes, which matters for a PreSync migration that failed at 2 a.m. Deleting hooks on success keeps the namespace tidy but removes the evidence you may need later. A common pattern is HookSucceeded for smoke tests and cleanup tasks, and HookFailed or the default for migrations, so failed runs stay inspectable while routine successes disappear. Decide this per hook with the team that owns the workload, and record the choice in the repository next to the manifest so reviewers can see it.

Test the policy on your own version before relying on it. Run a sync that succeeds and one that fails, then list the Jobs in the destination namespace and confirm which ones remain. Check that a second sync with a fixed name does not collide with a leftover object, and that your log retention or log shipping captures output before a hook is deleted. Add the check to your upgrade runbook, because a change in default behavior between releases would otherwise appear first as a stuck sync or a namespace full of old Jobs.

Security and operational risks of ordered hooks

Hooks are ordinary Kubernetes workloads that Argo CD creates during a sync, so they inherit the trust placed in the repository, the service account and the destination cluster. The cards below cover where that trust sits and what to check before a hook touches production data.

Hook Jobs carry cluster permissions

A PreSync or PostSync hook is a Job that Argo CD creates in the destination cluster and runs under a service account. Whatever that account can do, the hook can do, including altering data or reading Secrets in its namespace. Before enabling hooks, confirm which service account the hook uses, scope its RBAC to the work it must do, and check that the destination namespace limits what it can reach.

Migrations are code from Git

A database migration hook is only as trustworthy as the commit that defines it. Anyone who can merge to the tracked branch can change what runs against production during the PreSync phase. Protect the branch with required reviews, signed commits where your process supports them, and restricted merge rights for the paths that contain hook manifests.

Repo-server executes repository content

The repo-server runs Helm and Kustomize against repository content to render manifests before any hook is applied. A hostile or compromised repository can therefore influence what gets rendered, and out-of-tree file references can reach files outside the intended path. Limit which repositories Argo CD can read, and review third-party charts and Kustomize bases before they feed hook rendering.

Partial state after hook failure

When a PreSync migration succeeds but a later Sync step fails, the database may already reflect a change the application does not yet support. Argo CD will not roll that back for you. Decide in advance whether the recovery path is a reverse migration, a restore from backup, or a forward fix, write it down, and rehearse it against a non-production copy of the data.

Keep secrets out of hook manifests

Hook manifests are stored in Git, rendered into Job specs and visible to anyone who can read the Application or the Job. Credentials placed directly in a hook's environment variables can leak through Git history, logs or the Kubernetes API. Reference Secrets that are managed by a dedicated secrets operator, and confirm with your security team which secret sources are approved.

Restrict who edits hook annotations

Hook behavior is controlled by annotations such as the hook phase and deletion policy. Changing these annotations changes when a Job runs or whether its evidence survives. Argo CD RBAC applies to the Application itself, so a user who can update the Application may be able to alter sync behavior. Review project and Application permissions so only the platform team can change hook configuration.

Questions teams ask before ordering deployments with Argo CD

How much effort does it take to add sync hooks to an existing Argo CD application?

Adding hooks to an existing application is usually a bounded change to manifests, but the effort depends on how many ordering dependencies already exist in the team's head rather than in code. Each hook is a Kubernetes Job or similar resource with an annotation such as argocd.argoproj.io/hook, and each one needs a container image, credentials, a retry plan and a deletion policy. The larger part of the work is mapping which resources must exist before others, testing each phase on a non-production cluster, and confirming that a failed hook leaves the cluster in a state the team can recover from. Ask your partner to scope the dependency map first, since that determines the number of hooks to write and test.

What does the work need from our team before it starts?

Three groups of people shape most of the work. Manifest owners explain which resources depend on which and which annotations already exist. Database administrators or the platform team provide access to the migration tooling, a non-production database and the rollback expectations for schema changes. Test data owners supply representative datasets or fixtures so that PostSync smoke tests check real behavior. Without these inputs, the hook design tends to rest on assumptions. Confirm who signs off on each hook's failure behavior before the build starts, because a PreSync migration that fails is a release decision, not only a technical one.

Can our own team build sync waves and hooks without outside help?

Yes, a team that already runs Argo CD and owns its manifests can build sync waves and hooks internally. The concepts are documented in the Argo CD sync phases and waves guide, and the syntax is small. What tends to be harder is the verification: testing that a PreSync job fails the sync cleanly, that a SyncFail cleanup runs, and that the deletion policies do not leave completed Jobs behind across many environments. Teams without recent experience with database migrations in Kubernetes often choose a partner for the design review and failure testing, then keep the routine changes in-house.

What drives the cost of an ordered deployment engagement up?

Cost is driven by the number of migrations and hook jobs, the number of environments and clusters that must behave the same way, and the compliance evidence the change process must produce. Each additional migration adds a design decision about ordering, rollback and idempotency. Each additional environment adds test cycles. Evidence needs, such as recorded test results, documented failure paths and retained logs, add documentation and review time. Recurring costs are small by comparison: they come from updating hooks when schemas change and reviewing failed syncs. Ask for a quote that lists hooks, environments and evidence items separately so each driver is visible.

How long does a first pilot usually take, and what changes the duration?

A first pilot is usually scoped to one application, one migration path and one non-production environment, and its duration depends on how clear the dependency map is when the work starts. The main variables are the number of hooks, whether migrations are already idempotent, the availability of a test database with realistic data, and how many approval steps the change process requires. Confirm the timeline in the written scope, which should name the environments, the hook list and the acceptance tests. Any duration not written in that scope should be treated as an estimate to challenge.

Does a failed PreSync hook block the sync?

Yes. In Argo CD, a PreSync hook runs before the Sync phase, and if it fails, the sync stops before the regular resources are applied. This is the behavior that makes database migrations safe to gate on, but it also means a broken migration job stops the release. Teams should confirm the retry behavior, the hook deletion policy and what happens to the SyncFail phase, since that phase is where cleanup typically runs. Test the failure path in a non-production environment, because the state left by a half-applied migration is the risk that matters most to operations.

How do sync waves differ from ordering by Kubernetes resource kind?

Sync waves give you explicit control: each resource receives an integer through the argocd.argoproj.io/sync-wave annotation, the default is 0, and Argo CD applies lower waves first with a short delay between waves. Ordering by resource kind is implicit, since Argo CD applies some kinds before others by built-in rules, so the order is not something you set per application. Waves are the better choice when a database, its secret and the deployment that reads them must follow a known sequence. Kind-based ordering can be enough for simple applications. Check the sync phases and waves guide before deciding, and note that waves combine with hooks rather than replacing them.

How DeployOpen scopes ordered deployments in your environment

Scoping starts with an inventory of what the rollout actually touches. For ordered deployments, that means listing every resource that needs a sync wave or hook, including database migration jobs, one-off data backfills, smoke-test jobs, and any Helm hooks already in use. We also map which Application owns each resource, which cluster and namespace it lands in, and whether any hook already exists in a previous release that must be cleaned up. Ask your team to produce this list before the first conversation, because wave numbers and hook ordering are only meaningful once the full set of dependencies is visible.

Next we agree on failure expectations. A PreSync migration that fails stops the sync before application pods change, but a PostSync smoke test that fails leaves the new version running. Your rollback expectations determine which hooks need a SyncFail cleanup path, which deletion policy suits each Job (HookSucceeded, HookFailed or BeforeHookCreation), and whether automated sync and self-heal are on during the rollout. These choices affect the test plan and the runbook, so they belong in the scope rather than in a later review. The Argo CD sync phases and waves guide documents the behavior we will verify against your Argo CD version.

DeployOpen implements this work inside your own infrastructure, in your Git repositories, clusters and Argo CD instance, and does not take custody of your deployment pipeline. The written scope names the Applications, hooks, test cases and deliverables, such as manifests, runbooks and verification evidence. Quotes follow from that written scope, and the main cost drivers are the number of Applications and hooks, the number of environments that must be tested, and how many failure paths the runbook must cover. Confirm with us which of these you need before work starts.

How DeployOpen works on Argo CD rollout ordering

DeployOpen scopes, builds, tests and documents the sync phase, wave and hook design for your Argo CD applications inside your own clusters. The work starts from your rollout dependencies and ends with a runbook your team can operate after handover.

How we scope

  • Map each application's rollout dependencies, such as schema migrations before new pods start and smoke tests after sync.
  • Agree in writing which resources get sync waves, which get hooks, and which phases are out of scope.
  • Confirm the Argo CD version in use, since hook phases such as PostDelete depend on the release that supports them.

How we build

  • Set wave annotations on each resource and document the ordering reason next to every manifest group.
  • Write PreSync, PostSync and SyncFail hooks with deletion policies so finished Jobs do not accumulate in the cluster.
  • Test ordering on a non-production cluster, including a failed PreSync migration and a failed smoke test.

How we hand over

  • Deliver a written record of the wave map, hook list, deletion policies and the sync wave delay in use.
  • Walk your platform and application teams through a failed-sync drill so they can read Argo CD sync status.
  • Document the decisions behind each ordering choice so future changes can be reviewed against them.

How we support

  • Engagements are fixed-scope, and changes to the ordering design go through a written change request.
  • Ongoing support, upgrades and continuous testing are available as a retainer for hook and wave maintenance.
  • Confirm with your team which Argo CD release line you run, since patch support covers only the three most recent minor versions.

Talk to our team.

Tell us what you're working on, whether it's a deployment, an audit, a security test or a cyber range. You'll speak with an engineer who can help you scope it.

  • 30-minute call: free, with no obligation.
  • NDA on request: we can sign before you share details.
  • Clear next steps: a scope and plan after the call.