# Workspace Startup Coordination Usage > [!NOTE] > This feature is experimental and may change without notice in future releases. Startup coordination is built around the concept of **units**. You declare units in your Coder workspace template using the `coder exp sync` command in `coder_script` resources. When the Coder agent starts, it keeps an in-memory directed acyclic graph (DAG) of all units of which it is aware. When you need to synchronize with another unit, you can use `coder exp sync start $UNIT_NAME` to block until all dependencies of that unit have been marked complete. ## What is a unit? A **unit** is a named phase of work, typically corresponding to a script or initialization task. - Units **may** declare dependencies on other units, creating an explicit ordering for workspace initialization. - Units **must** be registered before they can be marked as complete. - Units **may** be marked as dependencies before they are registered. - Units **must not** declare cyclic dependencies. Attempting to create a cyclic dependency will result in an error. ## Requirements > [!IMPORTANT] > The `coder exp sync` command is only available from Coder version >=v2.30 onwards. To use startup dependencies in your templates, you must: - Enable the Coder Agent Socket Server. - Modify your workspace startup scripts to run in parallel and declare dependencies as required using `coder exp sync`. ### Enable the Coder Agent Socket Server The agent socket server provides the communication layer for startup coordination. To enable it, set `CODER_AGENT_SOCKET_SERVER_ENABLED=true` in the environment in which the agent is running. The exact method for doing this depends on your infrastructure platform:
#### Docker / Podman ```hcl resource "docker_container" "workspace" { count = data.coder_workspace.me.start_count image = "codercom/enterprise-base:ubuntu" name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}" env = [ "CODER_AGENT_SOCKET_SERVER_ENABLED=true" ] command = ["sh", "-c", coder_agent.main.init_script] } ``` #### Kubernetes ```hcl resource "kubernetes_pod" "main" { count = data.coder_workspace.me.start_count metadata { name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}" namespace = var.workspaces_namespace } spec { container { name = "dev" image = "codercom/enterprise-base:ubuntu" command = ["sh", "-c", coder_agent.main.init_script] env { name = "CODER_AGENT_SOCKET_SERVER_ENABLED" value = "true" } } } } ``` #### AWS EC2 / VMs For virtual machines, pass the environment variable through cloud-init or your provisioning system: ```hcl locals { agent_env = { "CODER_AGENT_SOCKET_SERVER_ENABLED" = "true" } } # In your cloud-init userdata template: # %{ for key, value in local.agent_env ~} # export ${key}="${value}" # %{ endfor ~} ```
### Declare Dependencies in your Workspace Startup Scripts
#### Single Dependency Here's a simple example of a script that depends on another unit completing first: ```bash #!/bin/bash UNIT_NAME="my-setup" # Declare dependency on git-clone coder exp sync want "$UNIT_NAME" "git-clone" # Wait for dependencies and mark as started coder exp sync start "$UNIT_NAME" # Do your work here echo "Running after git-clone completes" # Signal completion coder exp sync complete "$UNIT_NAME" ``` This script will wait until the `git-clone` unit completes before starting its own work. #### Multiple Dependencies If your unit depends on multiple other units, you can declare all dependencies before starting: ```bash #!/bin/bash UNIT_NAME="my-app" DEPENDENCIES="git-clone,env-setup,database-migration" # Declare all dependencies if [ -n "$DEPENDENCIES" ]; then IFS=',' read -ra DEPS <<< "$DEPENDENCIES" for dep in "${DEPS[@]}"; do dep=$(echo "$dep" | xargs) # Trim whitespace if [ -n "$dep" ]; then coder exp sync want "$UNIT_NAME" "$dep" fi done fi # Wait for all dependencies coder exp sync start "$UNIT_NAME" # Your work here echo "All dependencies satisfied, starting application" # Signal completion coder exp sync complete "$UNIT_NAME" ```
## Best Practices ### Test your changes before rolling out to all users Before rolling out to all users: 1. Create a test workspace from the updated template 2. Check workspace build logs for sync messages 3. Verify all units reach "completed" status 4. Test workspace functionality Once you're satisfied, [promote the new template version](../../../reference/cli/templates_versions_promote.md). ### Handle missing CLI gracefully Not all workspaces will have the Coder CLI available in `$PATH`. Check for availability of the Coder CLI before using sync commands: ```bash if command -v coder > /dev/null 2>&1; then coder exp sync start "$UNIT_NAME" else echo "Coder CLI not available, continuing without coordination" fi ``` ### Complete units that start successfully Units **must** call `coder exp sync complete` to unblock dependent units. Use `trap` to ensure completion even if your script exits early or encounters errors: ```bash SYNC_STARTED=0 if coder exp sync start "$UNIT_NAME"; then SYNC_STARTED=1 fi cleanup_sync() { if [ "$SYNC_STARTED" -eq 1 ]; then coder exp sync complete "$UNIT_NAME" fi } trap cleanup_sync EXIT ``` ### Use descriptive unit names Names should explain what the unit does, not its position in a sequence: - Good: `git-clone`, `env-setup`, `database-migration` - Avoid: `step1`, `init`, `script-1` ### Prefix a unique name to your units When using `coder exp sync` in modules, note that unit names like `git-clone` might be common. Prefix the name of your module to your units to ensure that your unit does not conflict with others. - Good: `.git-clone`, `.claude` - Bad: `git-clone`, `claude` ### Document dependencies Add comments explaining why dependencies exist: ```hcl resource "coder_script" "ide_setup" { # Depends on git-clone because we need .vscode/extensions.json # Depends on env-setup because we need $NODE_PATH configured script = <<-EOT coder exp sync want "ide-setup" "git-clone" coder exp sync want "ide-setup" "env-setup" # ... EOT } ``` ### Avoid circular dependencies The Coder Agent detects and rejects circular dependencies, but they indicate a design problem: ```bash # This will fail coder exp sync want "unit-a" "unit-b" coder exp sync want "unit-b" "unit-a" ``` ## Frequently Asked Questions ### How do I identify scripts that can benefit from startup coordination? Look for these patterns in existing templates: - `sleep` commands used to order scripts - Using files to coordinate startup between scripts (e.g. `touch /tmp/startup-complete`) - Scripts that fail intermittently on startup - Comments like "must run after X" or "wait for Y" ### Will this slow down my workspace? No. The socket server adds minimal overhead, and the default polling interval is 1 second, so waiting for dependencies adds at most a few seconds to startup. You are more likely to notice an improvement in startup times as it becomes easier to manage complex dependencies in parallel. ### How do units interact with each other? Units with no dependencies run immediately and in parallel. Only units with unsatisfied dependencies wait for their dependencies. ### How long can a dependency take to complete? By default, `coder exp sync start` has a 5-minute timeout to prevent indefinite hangs. Upon timeout, the command will exit with an error code and print `timeout waiting for dependencies of unit ` to stderr. You can adjust this timeout as necessary for long-running operations: ```bash coder exp sync start "long-operation" --timeout 10m ``` ### Is state stored between restarts? No. Sync state is kept in-memory only and resets on workspace restart. This is intentional to ensure clean initialization on every start.