{"id":39005660,"url":"https://github.com/pulcy/j2","last_synced_at":"2026-01-17T17:16:51.043Z","repository":{"id":57552990,"uuid":"51771662","full_name":"pulcy/j2","owner":"pulcy","description":"Pulcy service deployment tool; Use same job specs for Kubernetes \u0026 Fleet","archived":false,"fork":false,"pushed_at":"2018-03-21T18:37:52.000Z","size":18713,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-04-08T10:40:54.559Z","etag":null,"topics":["cluster","deployment","kubernetes"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pulcy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-02-15T17:13:48.000Z","updated_at":"2024-11-28T12:29:06.000Z","dependencies_parsed_at":"2022-09-26T18:50:53.865Z","dependency_job_id":null,"html_url":"https://github.com/pulcy/j2","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/pulcy/j2","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pulcy%2Fj2","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pulcy%2Fj2/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pulcy%2Fj2/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pulcy%2Fj2/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pulcy","download_url":"https://codeload.github.com/pulcy/j2/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pulcy%2Fj2/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28512253,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-17T13:38:16.342Z","status":"ssl_error","status_checked_at":"2026-01-17T13:37:44.060Z","response_time":85,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cluster","deployment","kubernetes"],"created_at":"2026-01-17T17:16:50.926Z","updated_at":"2026-01-17T17:16:51.026Z","avatar_url":"https://github.com/pulcy.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# J2\n\n[`j2`](#why-is-it-called-j2) is the pulcy service deployment tool.\nIt takes a job description as input and generates (fleet) unit files for all tasks in the given job.\nThe unit files will be pushed onto a CoreOS cluster.\n\n##  Usage\n\nTo create/update a [job](#job-specification) on a [cluster](#cluster-specification), run:\n\n```\nj2 run -j \u003cjobpath\u003e -c \u003cclusterpath\u003e [-o \u003coptionspath]\n```\n\nTo completely remove a job from a cluster, run:\n\n```\nj2 destroy -j \u003cjobpath\u003e -c \u003cclusterpath\u003e\n```\n\n## Job specification\n\nA job is a logical group of services.\n\nA job contains one or more task groups.\nA task group is a group of tasks that are scheduled on the same machine.\nA task specifies a single container.\n\nA very basic job looks like this:\n\n```\njob \"basic\" {\n    task \"someservice\" {\n        image = \"myimage\"\n    }\n}\n```\n\nJobs are specified in [HCL format](https://github.com/hashicorp/hcl).\nA job always has a name and only one job can be specified per file.\n\nObjects in a job can be `task`, `group` and `constraint`.\nIf you add a `task` directly to a `job`, it will be automatically wrapped in a `task-group`.\n\nThe following keys can be specified on a `job`.\n\n- `id` - `id` is used to give a job a unique identifier which is used for authentication when fetching secrets.\n- `constraint` - See [Constraints](#constraints)\n\n### Tasks\n\nA `task` is an object that specifies something that will be executed in a container.\n\nThe following keys can be specified on a `task`.\n\n- `args` - Contains the command line arguments passed during the start of the container.\n- `environment` - Contains zero of more environment variables passed during the start of the container.\n- `image` - Specifies the docker image that will be executed.\n- `type` - The type of a task can be \"service\" (default), \"oneshot\" or \"proxy\".\n  Oneshot tasks are supposed to run for a while and then exit.\n  Service tasks are supposed to run continuously and will be restarted in case of failure.\n  Proxy tasks do not have a real service to run, instead they provide a virtual\n  service that forwards all requests to another service (optionally with a rewrite rule).\n- `after` - Contains the name of zero or more other tasks in the same group. If set, this task will be started\n  only after all listed tasks have been started.\n- `volumes-from` - Contains the name of zero or more other tasks. The volumes used by these other tasks will be\n  mounted in the container of this task.\n- `volumes` - Contains a list of zero or more volume mounts for the container.\n  Each volumes entry must be a valid docker volume description (\"hostpath:containerpath\")\n- `ports` - Contains a list of port specifications that specify which ports (exposed by the container) will be\n  mapped into the port namespace of the machine on which the container is scheduled.\n  Each port entry must be a valid docker port specification.\n  Note that `ports` are not often used. In most cases you'll use a `frontend` or `private-frontend`.\n- `links` - Contains a list of task names that this task will be able to access through their private frontends.\nEach name must be a fully qualified task name (job.group.task).\n- `capabilities` - Contains a list of Linux capabilities to add to the container. (See `docker run --cap-add`)\n- `constraint` - See [Constraints](#constraints)\n- `http-check-path` - Contains an HTTP path for the load-balancer to call when checking the status of this task.\n- `frontend` - Contains a public load-balancer registration. This configures the load-balancer to forward certain requests from the public network interface(s) of the cluster to this task. See [Frontends](#frontends).\n- `private-frontend` - Contains a private load-balancer registration. This configures the load-balancer to forward certain requests from the private network interface(s) of the cluster to this task. See [Frontends](#frontends).\n- `secret` - Contains a specification for a secret value to be fetched and mapped into the container. See [Secret](#secrets).\n- `log-driver` - Specifies the log-driver to use in docker. This can be \"\" (default) or \"none\". If not equal to \"none\",\nthe `log-args` or the `docker` settings in the [cluster](#cluster-specification) are used.\n- `target` - The name of the task to forward requests to.\n  Only used with `type==proxy`.\n\n#### Frontends\n\nFrontends are used to provide a configuration for the load-balancer.\n\nThe following keys can be specified on a public frontend.\n\n- `domain` - The load-balancer will forward requests that match this domain.\n- `path-prefix` - The load-balancer will forward requests where the path of the requests starts with this prefix.\n- `ssl-cert` - The load-balancer will use an SSL certificate with this filename for connections to this task. If you do not specify an SSL certificate and the load-balancer is configured to use [Let's Encrypt](https://letsencrypt.org) a certificate will be automatically created for the specified `domain`.\n\nThe following keys can be specified on a all frontends.\n\n- `port` - The load-balancer will forward requests to this port of the task. If you do not specify a port, it will forward requests to any of the ports exposed by the container.\n- `host-port` - The load-balancer will listen on this port for requests intended for the task. If you do not specify a host-port, standard ports will be assumed.\n- `mode` - Specifies the mode the load-balancer will be configured for for this frontend. Mode can be `http` (default) or `tcp`.\nFrontends using `tcp` mode with a `domain` setting will offer TCP over TLS connections, using SNI to identify the correct task to forward the request to.\nThe connection from the load-balancer to the task will use TCP only.\nFrontends using `tcp` mode without a `domain` setting request a `host-port` setting.\n- `user` - User objects specify password authentication to be used for requests forwarded for this task.\n- `weight` - Contains a value [0...100] used to order frontend specifications in the load-balancer. If 2 frontend specifications both match a specific request, the one with the highest weight will be used.\n\nThe following keys can be specified on a private frontend.\n\n- `register-instance` - If set, instances of this task will also be registered in the load-balancer under an instance specific\nname. This enables access to individual instances, in addition to load-balanced access to all instances of a task.\n\n#### Secrets\n\nSecrets are used to pass sensitive data to tasks in a secure manor.\nThe sensitive data can be exposed as an environment variable (e.g. passwords) or as a file (e.g. certificates).\nSecrets are extracted from a [Vault](https://vaultproject.io).\n\nA secret looks like this:\n\n```\nsecret \"secret/mypassword\" {\n    environment = \"MYPASSWORD\"\n}\n```\n\nThe above secret results in the value of a secret under path \"secret/mypassword\" to be passed to the task in an environment variable named \"MYPASSWORD\".\n\n```\nsecret \"secret/mycertificate\" {\n    file = \"/config/mycertificate.pem\"\n}\n```\n\nThe above secret results in the value of a secret under path \"secret/mycertificate\" to be passed to the task in a file mounted on \"/config/mycertificate.pem\".\n\nThe following keys can be specified on a `secret`.\n\n- `field` - Contains the name of the field of the secret. If no field is specified, the `value` field is fetched.\n- `environment` - Contains the name of the environment variable that will be passed into the container.\n- `file` - Contains the full path of the file that will be mounted into the container.\n\nYou must specify an `environment` or a `file`, not both.\n\n### Task groups\n\nA `group` is an object that groups one or more tasks such that they are always scheduled on the same machine.\nYou use the `count` key to specify how many instances of a task-group you want to run. Each instance of a task-group\ncontains a container for all tasks in that task-group. Multiple instances of a task-group are not guaranteed to run on\nthe same machine. In fact you often want different instances to run on different machines for high availability.\n\nThe following keys can be specified on a `group`.\n\n- `count` - Specifies how many instances of the task-group that should be created.\n- `global` - If set to true, this task-group will create one instance for every machine in the cluster.\n- `constraint` - See [Constraints](#constraints)\n- `restart` - If set to `all`, all tasks of this group will be restarted in case one of them restarts (or is updated).\n\n### Constraints\n\nWith constraints you can control on which machines tasks can be scheduled.\nConstraints can be specified on `job`, `group` and `task` level. Constrains on a deeper level overwrite constraints\non high levels with the same `attribute`.\n\nHere's an example of a constraint that forced a task to be scheduled on a machine that has `region=eu-west` in\nits metadata.\n\n```\nconstraint {\n    attribute = \"meta.region\"\n    value = \"eu-west\"\n}\n```\n\nThe following keys can be specified on a `constraint`.\n\n- `attribute` - One of the attributes you can filter on. See [Attributes](#attributes) below.\n- `value` - The value for this attribute.\n\n#### Attributes\n\nThe following attributes can be used in constraints.\n\n- `meta.\u003ckey\u003e` - Refers to a key used in the metadata of a machine.\n- `node.id` - Refers to the `machine-id` of a machine.\n\n## Cluster specification\n\nA cluster file specifies those attributes of a cluster that are relevant for deploying jobs on it.\n\nA typical cluster file looks like:\n\n```\ncluster \"production\" {\n    domain = \"example.com\"\n    stack = \"production\"\n    instance-count = 3\n\n    default-options {\n        \"force-ssl\" = \"true\"\n    }\n}\n```\n\nA cluster file for a cluster provisioned by [Gluon](https://github.com/pulcy/gluon) can look like this:\n\n```\ncluster \"production\" {\n    domain = \"example.com\"\n    stack = \"production\"\n    instance-count = 3\n\n    docker {\n        log-args = [\"--log-driver=fluentd\", \"--log-opt fluentd-address=127.0.0.1:24284\"]\n    }\n\n    fleet {\n        after = [\n            \"gluon.service\",\n            \"loggly-fluentd-fluentd-mn.service\"\n        ]\n        wants = \"loggly-fluentd-fluentd-mn.service\"\n    }\n\n    default-options {\n        \"force-ssl\" = \"true\"\n    }\n}\n```\n\nThe following keys can be specified on a `cluster`.\n\n- `domain` - The domain name of the cluster\n- `stack` - The name of the stack to deploy in. The combination of `stack` + `domain` forms the DNS name that is\nused to deploy units to.\n- `instance-count` - The number of machines in the cluster. If this number is higher than the `count` of a task-group,\ndifferent instances of that task-group will be forced on different machines.\n- `docker` - A set of options applied to all docker commands generated for jobs on this cluster.\n- `docker.log-args` - Docker command line arguments related to logging. Uses when `log-driver` is not equal to `none`.\n- `fleet` - A set of options applied to all fleet units generated for jobs on this cluster.\n- `fleet.after` - A list of unit names to add to the [After](https://www.freedesktop.org/software/systemd/man/systemd.unit.html#After=) setting of each unit.\nThis list is filtered such that units originating from the same job are excluded.\n- `fleet.wants` - A list of unit names to add to the [Wants](https://www.freedesktop.org/software/systemd/man/systemd.unit.html#Wants=) setting of each unit.\nThis list is filtered such that units originating from the same job are excluded.\n- `fleet.requires` - A list of unit names to add to the [Requires](https://www.freedesktop.org/software/systemd/man/systemd.unit.html#Requires=) setting of each unit.\nThis list is filtered such that units originating from the same job are excluded.\n- `fleet.global-instance-constraints` - A list of metadata constraints. One of them is added to an instance of a global\nunit with a `count` higher than 1.\nFor example with `global-instance-constraints = [\"global=1\", \"global=2\"]` instance 1 will get `global=1` and instance 2\nwill get `global=2` as metadata. You can choose how to spread these metadata's across the machines of the cluster, but make\nsure that every machine has `global=1` OR `global=2` in its metadata and not both.\n\n## Why is it called J2?\n\nThis tool is named after the famous [J-2](https://en.wikipedia.org/wiki/J-2_%28rocket_engine%29) rocket engine that helped bring man to the moon. It was a predecessor for the RS-25 rocket engine that powered the Space Shuttle and even today it is an inspiration for the J-2X engine intended for NASA's Space Launch System.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpulcy%2Fj2","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpulcy%2Fj2","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpulcy%2Fj2/lists"}