Architect components have the concept of a Task, similar to a cron job. Since Tasks are defined in a component in a similar way to Services, you can configure your Tasks with dependencies on other components, take secrets, or call a service defined in the same component.

This guide will walk through the process of creating a new component with a Task that exercises most of the features available to Tasks.

  1. Create an example container and script to run as a Task
  2. Defining a minimal Component with a Task, and execute it locally
  3. Updating the Component to improve local development experience
  4. Tasks with Secrets
  5. Tasks with Dependencies

Create a Script and Dockerfile for our Task

Before getting started with the components, create a script for your task, and a dockerfile so you can bake the script into an image.

% mkdir ./my-task
% cd ./my-task
% touch Dockerfile my-task.sh

The Dockerfile:

FROM alpine:3.14
COPY my-task.sh /my-task.sh
CMD ["sh", "-c", "/my-task.sh"]

The my-task.sh:

#!/usr/bin/env sh
echo "${GREETING:-Hello}, world!"

With those created, go ahead and build the docker image and execute it, just to verify it’s working as expected.

% docker build . -t my-task:latest -q
sha256:d7116f02d2220db058c3df25417894fae557ae55875c9abb5f14946aa6c9b8ee

% docker run my-task:latest
Hello, world!

% docker run -e GREETING=Hola my-task:latest
Hola, world!

You can see that when the task runs without the GREETING environment variable, it displayed the typical “Hello, world!”, but will provide a more customized message when the GREETING variable is provided. With the docker image working, proceed with creating the Architect Component and Task.

Define a Minimal Component Task

In your my-task directory, create an architect file to define your component:

touch architect.yml

Now you can create a Task in the component file just as you would a Service. In this case you’ll give the component a brief description, a blank schedule so it runs only when manually executed, and finally you provide the build context as the local directly. The Component Task docs cover all the nuances in the task definition, as do the Task Reference docs.

The architect.yml:

name: my-task
description: A hello world task! 👋

tasks:
  hello-world:
    schedule: ""
    build:
      context: ./

You can validate the component to verify aren’t any mistakes:

% architect validate ./architect.yml
✅ my-task: /Users/test/development/my-task/architect.yml

To run this locally, you’ll need to link the component by running:

% architect link ./architect.yml
Successfully linked my-task to local system at /Users/test/development/my-task/architect.yml.

Now that the component is linked, you can deploy it locally using the architect dev command. Under the hood, this creates a docker-compose file which will be used as the task is executed.

% architect dev my-task
...

And now you can execute the task locally:

% architect task:exec --local my-task hello-world
Running task my-task/hello-world:latest in the local architect environment...

Creating architect-task-hello-world-xhkltnus_run ...
Creating architect-task-hello-world-xhkltnus_run ... done
Hello, world!


Successfully ran task.

And there you have it; you’ve executed your task locally! But you may want to improve the component definition to reduce friction during development.

Local Task Development

Development generally requires rapid iteration, and needing to run dev and task:exec each time you want to test can be a tedious experience. To alleviate this, you can make use of the debug feature to mount our source code when you run the task without having to re-deploy. This can be achieved by updating the component with a debug block, re-deploying (just the once) to update the component configuration. You will only use a few of the available local development features, but more info can be found in the Local Configuration docs.

Now you can update the architect file with the debug block containing a volume mount that overrides the script in the container with the script from the local filesystem:

tasks:
   hello-world:
     schedule: ""
     build:
       context: ./
+    debug:
+      volumes:
+        src:
+          mount_path: /my-task.sh
+          host_path: ./my-task.sh

You will have to redeploy the component because you changed its definition, so go ahead and redeploy and execute it again to make sure everything is still working as expected.

% architect dev my-task
...
% architect task:exec --local my-task hello-world
...
Hello, world!

Excellent! With this version of your component, the my-task.sh script is being mounted into the container and used when the task is executed. You should make a change to the script and re-execute the task to verify this:

Change the my-task.sh file to include a waving emoji in the output:

#!/usr/bin/env sh
-echo "${GREETING:-Hello}, world!"
+echo "${GREETING:-Hello}, world! 👋"

Now you’ll see the emoji in the output when you execute the task again without re-registering or re-deploying the component:

% architect task:exec --local my-task hello-world
...
Hello, world! 👋

Tasks with Secrets

The task you created supports a GREETING environment variable, so you can make use of that as you deploy the component by setting a value as a secret. In addition to adding the secrets section to the component, you’ll also need to declare the GREETING environment variable on the task, and assign it to the value given by the secret. More information about secrets can be found in the Components Secrets docs and the Secrets Reference docs.

The architect.yml file:

name: my-task
 description: A hello world task! 👋
+secrets:
+  greeting:
+    required: false
+    description: The greeting to use
+
 tasks:
   hello-world:
     schedule: ""
     build:
       context: ./
+    environment:
+      GREETING: ${{ secrets.greeting }}
     debug:
       volumes:
         src:

Now you can redeploy the component with a secret value, whose value will be used when the task is executed. It’s important to understand that these secrets are set at deploy time, so you cannot re-declare the GREETING value later when you execute the task.

% architect dev my-task -s greeting=Hola
...
% architect task:exec --local my-task hello-world
Hola, world! 👋
...

Tasks with Dependencies

As mentioned earlier, tasks can use the same facilities as services which also means a Task can depend on a Service, just as Services can depend on other Services. So you can expand the component to include a service for generating names to use in your task’s greeting.

To start, add a really simple node server in a file called server.js, and make a dedicated docker image.

% mkdir ./server
% touch ./server/server.js
% touch ./server/Dockerfile

The server/server.js:

const http = require("http");

const names = ["April", "Katie", "Marlon", "Darnell", "Simon", "Lena", "Noah"];

const requestListener = function (req, res) {
  const name = names[(names.length * Math.random()) | 0];
  res.writeHead(200);
  res.end(name);
};

const server = http.createServer(requestListener);
server.listen(9000);

The server/Dockerfile:

FROM node:16-alpine3.11
COPY server.js /server.js
CMD ["node", "/server.js"]

The server will respond with just the name in the body, so you can update the script to simply curl the server address provided by the API_URL variable.

#!/usr/bin/env sh
-echo "${GREETING:-Hello}, world! 👋"
+echo "${GREETING:-Hello}, $(curl -s $API_URL)! 👋"

Now go ahead and add the service to the architect file, and then pass the URL for the service into the task using the environment variable referenced in the script:

required: false
     description: The greeting to use
+services:
+  name-generator:
+    build:
+      context: ./server
+    interfaces:
+      main:
+        port: 9000
+
 tasks:
   hello-world:
     schedule: ""
@@ -13,6 +24,7 @@ tasks:
       context: ./
     environment:
       GREETING: ${{ secrets.greeting }}
+      API_URL: ${{ services.name-generator.interfaces.main.url }}
     debug:
       volumes:
         src:

Great! Now you can deploy your changes.

% architect dev --detached my-task -s greeting=Hola
Building containers... done

Once the containers are running they will be accessible via the following urls:
http://localhost:50006/ => my-task-name-generator-0s8tot3o:9000

Starting containers...

And now when you execute the task again, you’ll see a greeting for a random name provided by the dependent service:

% architect task:exec --local my-task hello-world ... Hola, Katie! 👋