L-337 Service
ACTIVE DEVELOPMENT!
This is currently in active development. We encourage you trying it out. We discourage you relying on it.
The l337
service is the lowest level api: 4
service and it implements Lando Specification 337 which is so low level that it's too low to be the default service. That means you need to explicitly set type: l337
to use it.
In high level terms it combines service orchestration and image specification into a single file format.
Specifically, it is a light superset around the Docker Compose Version 3 format that also uses the Dockerfile specification.
This means that you should be able to paste Docker Compose and/or Dockerfile compatible contents into your Landofile, add api: 4
to each service and have it "just work".
As noted above Lando Specification 337 extends the Docker Compose 3 spec with Dockerfile stuff. That means that everything in both of those specs is supported by default. So this is a valid Landofile:
Landofile
name: "my-app"
services:
# this is a Lando 3 php service
php:
type: "php:7.4"
api: 3
# these are Lando 4 "l337" services
web:
api: 4
type: l337
image: "nginx:1.15"
networks:
my-network:
volumes:
- "./:/app"
- "my-data:/data"
db:
api: 4
type: l337
build:
dockerfile: "./Dockerfile"
networks:
my-network:
volumes:
my-data:
OK cool, got it, but how does Dockerfile stuff factor in? What is actually different in the spec besides api
?
Two questions.
One Answer: image
That's it.
Image
Lando Specification 337 is identical to the Docker Compose spec with the exception of the image
key which now handles different string inputs and has an extended object format for MOAR POWAH.
The string input for image
has been extended and now allows the below:
Landofile
name: "my-app"
services:
# a valid registry image, eg the original Docker Compose usage
example-1:
api: 4
type: l337
image: "nginx:1.21"
# a path to a Dockerfile compatible file
example-2:
api: 4
type: l337
image: "./images/nginx/Dockerfile"
# raw Dockerfile compatible instructions
example-3:
api: 4
type: l337
image: |
FROM nginx:1.21
ENV HELLO there
...
CMD run-stuff
You can extend image
into object format to get access to more features. Here is an example that implements all the keys available in object format.
Landofile
name: "my-app"
services:
example-1:
api: 4
type: l337
image:
imagefile: "nginx:1.21"
tag: "pirog/nginx:1.21"
buildkit: true
ssh: false
args:
vibe: rising
context:
- "./nginx.conf:/etc/nginx/conf.d/default.conf"
groups:
- reallllllearly: -8675309
steps:
- instructions: "RUN id > /tmp/user"
group: "reallllllearly"
- instructions: |
ENV VIBES rising
RUN id > /var/ww/index.html
group: "reallllllearly-10-before-root"
Imagefile
The image
string notation format above actually populates the imagefile
key behind the scenes. For that reason, this usage is the same as above but with a different key.
Use context instead of COPY/ADD
While you can use COPY
and ADD
instructions here we recommend you use context
instead. See this for more information.
Landofile
name: "my-app"
services:
# a valid registry image
example-1:
api: 4
type: l337
image:
imagefile: nginx:1.21
# a path to a Dockerfile compatible file
example-2:
api: 4
type: l337
image:
imagefile: ./images/nginx/Dockerfile
# raw Dockerfile compatible instructions
example-3:
api: 4
type: l337
image:
imagefile: |
FROM nginx:1.21
ENV HELLO there
...
CMD run-stuff
Note that you can also use dockerfile
instead of imagefile
if you prefer that usage. If you use both imagefile
will win.
Build Args
You can pass in build args that are then used by your image
instructions. Both array
and object
notation is permissible but object
is probably better.
Landofile
name: "my-app"
services:
build-args-1:
api: 4
type: l337
image:
args:
- nginx=1.19.2
- vibe=rising
- something
dockerfile: "./images/nginx/Vibefile"
build-args-2:
api: 4
type: l337
image:
args:
nginx: 1.21.5
vibe: dialed
something:
imagefile: |
ARG nginx=1.21.4
FROM nginx:${nginx}
ARG vibe
ENV VIBE=${vibe}
Array formatted key=value
pairs are trimmed in both key
and value
. Args with no value
are ignored as in something
above.
Buildkit
If for some reason you prefer to use the old pre-BuildKit image builder you can toggle this to false
.
Landofile
name: "my-app"
services:
# use the legacy builder
example-1:
api: 4
buildkit: false
type: l337
image:
imagefile: nginx:1.21
Note that if you do buildkit: false
some image build features, specifically those in Dockerfile 1.1.0+ and the ssh features of this service, may not be available. You can also use buildx
as an alias for buildkit
. If you use both it will evaluate to true
if either is true
. Really just don't use both 😉!
You probably should just ignore this setting unless you have a well understood reason to do otherwise.
Context
If you would like to COPY
and/or ADD
files into your build context and image then use context
. Many forms and options are supported:
Landofile
name: "my-app"
services:
example-1:
api: 4
type: l337
image:
imagefile: "nginx:1.21"
context:
# COPY ./folder to /folder
- "./folder"
# COPY ./folder to /thing
- "./folder:thing"
# COPY file1 to /file2
- "file1:/file2"
# COPY file1 to /file3
- src: "file1"
dest: "file3"
# COPY file1 to /file4
- source: "file1"
destination: "file4"
# COPY ./images/Dockerfile to /images/Dockerfile
- source: "./images/Dockerfile"
# COPY file1 to /file6 and set ownership to nginx:nginx with perms 775
- source: "file1"
destination: "file6"
owner: "nginx:nginx"
permissions: "775"
# COPY file1 to /file7 and set ownership to nginx:nginx
- source: "file1"
destination: "file7"
user: "nginx"
group: "nginx"
# ADD HeresAHealthToTheCompany.json
# to /SeaShanties/lyrics/main/shanties/HeresAHealthToTheCompany.json
- source: "https://raw.githubusercontent.com/SeaShanties/lyrics/main/shanties/HeresAHealthToTheCompany.json"
# ADD available-shanties.json
# to /etc/config/available-shanties.json and set ownership to blackbeard
- source: "https://raw.githubusercontent.com/SeaShanties/lyrics/main/available-shanties.json"
dest: "/etc/config/available-shanties.json"
owner: "eddie-teach"
Groups
groups
allow you to organize steps
.
By default every l337
service has two groups, default
and context
, with the following values:
context:
description: "A group for adding and copying sources to the image"
weight: 0
user: "root"
default:
description: "A default general purpose build group around which other groups can be added"
weight: 1000
user: "root"
You can add additional groups into your Landofile and then use them in your steps
.
Landofile
name: "my-app"
services:
example-1:
api: 4
type: l337
image:
imagefile: "nginx:1.21"
groups:
# adds a group called "val-jean" with weight "24601"
# uses root user by default
- val-jean: 24601
# adds a root user group called "system" that runs first
- name: "system"
description: "Install system packages and stuff"
weight: -10000
user: "root"
# adds a nginx user group called "user" that runs last
- name: "user"
description: "Allows for user run commands after other groups"
weight: 10000
user: "nginx"
SSH
If your image build requires access to ssh
keys you have a few options:
Landofile
name: "my-app"
services:
example-1:
api: 4
type: l337
image:
imagefile: Dockerfile
# make all passphraseless keys and $SSH_AUTH_SOCK available
ssh: true
example-2:
api: 4
type: l337
image:
imagefile: Dockerfile
# only make the keys available
ssh:
agent: false
keys: true
example-3:
api: 4
type: l337
image:
imagefile: Dockerfile
# only make the agent available
ssh:
agent: true
keys: false
example-4:
api: 4
type: l337
image:
imagefile: Dockerfile
# use particular agent and keys
ssh:
agent: /run/ssh/agent.pid
keys:
# can use a key
- /home/.ssh/id_special
# or load all the keys in a dir
- /home/.ssh-more
example-5:
api: 4
type: l337
image:
imagefile: Dockerfile
# use particular agent via envar and a single key
ssh:
agent: $SSH_AUTH_SOCK_THING
keys: /home/.ssh/id_rsa
As you can see its pretty configurable but we recommend that in most cases you just set ssh: true
if you need ssh
stuff as this is going to be your most portable option.
Here is a concrete example that uses a passphrase protected key that is first loaded into the ssh-agent
and a few other passphrase-less keys that are in ~/.ssh
.
Keys
~/.ssh/id_passphrased
~/.ssh/id_no_pp_1
~/.ssh/id_no_pp_2
Landofile
name: "my-app"
services:
example-1:
api: 4
type: l337
image:
ssh: true
imagefile: |
FROM debian
# will clone with $SSH_AUTH_SOCK which contains id_passphrased
RUN --mount=type=ssh,id=agent git clone git@github.com:lando/lando.git /lando2
# will clone with either id_no_pp_1 or id_no_pp_2
RUN --mount=type=ssh,id=keys git clone git@github.com:lando/lando.git /lando
# will clone with id_no_pp_1
RUN --mount=type=ssh,id=id_no_pp_1 git clone git@github.com:lando/lando.git /lando3
Execution
# start agent
eval "$(ssh-agent -s)"
# add passphrase protected key
ssh-add ~/.ssh/id_passphrased
# rebuild images
lando rebuild
Here are some additional notes on this feature:
- Passphrase protected keys can be used but only if added to your host
ssh-agent
before image build - This feature is not available if you set
buildkit: false
- The keys/agent are only available when explicitly mounted in a
RUN
instruction they DO NOT end up in the resulting image
Steps
While you can specify an entire Imagefile's contents directly in image.imagefile
it's often better to use steps
which are ordered and partial Dockerfile-compatible instructions.
Because steps
are wrapped in a standardized and ordered layer, Lando and any of its plugins can insert instructions
into the resulting imagefile
wherever they make the most sense. This allows for great flexibility.
However, we are mostly interested here in how steps
can be used directly in a Landofile.
Landofile
name: "my-app"
services:
example-1:
api: 4
type: l337
image:
imagefile: "nginx:1.21"
groups:
... # as defined [above](#groups) and omitted here for brevity
steps:
# insert string instructions into the default group
- instructions: |
ENV VIBES RISING
RUN apt-get update -y
RUN /my-script.sh
# insert array format instructions into the default group
# See: https://www.npmjs.com/package/dockerfile-generator for syntax
- instructions:
- env:
KIRK: "wesley"
SPOCK: "peck"
- run: "env"
- run:
- "stat"
- "/tmp"
# insert group detached, one-off, singleton, instructions
# at arbitrary weight
- instructions: "ENV PIKE mount"
weight: 1001
- instructions: |
RUN echo "last" >> /stuff
weight: 999
- instructions: |
RUN echo "first" >> /stuff
weight: 1
# insert instructions into groups
- instructions: |
ENV KIRK pine
ENV SPOCK quinto
RUN id > /system-user
group: "system"
- instructions: "RUN id > /tmp/user"
group: "user"
# insert instructions using group-override format
# runs -4 weight units before the system group
- instructions: |
ENV KIRK shatner
ENV SPOCK nimoy
group: "system-4-before"
# run 10 weight units after the user group but uses the root user
- instructions: |
ENV KIRK shatner
ENV SPOCK nimoy
group: "user-10-root"
Note that if you specify both a group
and a weight
the step will run at the weight
regardless of the weight of the group. Generally it's a good idea to not use both group
and weight
in the same step.
If you reference a group
that does not exist then the default
group will be used.
COPY/ADD
While you can use COPY
and ADD
instructions here we recommend you use context
instead. See this for more information.
Override syntax
The group override syntax is flexible as long as the parent group is first. For example the following overrides are equivalent:
system-2
system-2-after
system-root-after-2
system-2-after-root
system-root-2-after
That said, we like the GROUP-OFFSET-DIRECTION-USER
format. 😉
Also note that it is totally possible to have defined two groups
like system
and system-4
. In this scenario a step
using the system-4
group will actually use the system-4
group and not override the system
group with a 4-after
offset.
Since this can easily get confusing it's best to be careful when defining your group names.
Tag
If you wish to force Lando to use a particular tag on successful image creation, you can.
Landofile
name: "my-app"
services:
tag-me-bro:
api: 4
type: l337
image:
imagefile: |
FROM nginx:1.21
ENV SERVER=apache
ENV CONFUSED=true
tag: "loki/apache:1.21"
Caveats
As you may have already suspected because the l337
service sits below the main lando
service it lacks ALL the features in that service. Using it is pretty close to just using Docker Compose/Dockerfiles straight up.
Said another way, you should really only use this service directly if you are intentionally looking to avoid normal Lando features or want to use something that is more-or-less like Docker Compose.
That said, here are the things that make it pretty close but not identical to Docker Compose/Dockerfiles:
1. Auto app mount discovery
If you volume
mount your app root directory then Lando will assume its mount destination as the app mount for tooling purposes. Consider the following example:
Landofile
name: "my-app"
services:
my-service:
api: 4
type: l337
image: "php:8.2-cli"
volumes:
- "./:/home"
tooling:
pwd:
service: "my-service"
Note that changing the directory on the host also changes the location at which lando pwd
is executed within the container:
lando pwd
# /home
cd subdir && lando pwd
# /home/subdir
2. Working dir support
If you do not mount your app root directory as above then Lando will use working_dir
to set the default tooling dir
for that service.
Landofile
name: "my-app"
services:
my-service:
api: 4
type: l337
image: "php:8.2-cli"
working_dir: "/var/www"
tooling:
pwd:
service: "my-service"
Note that lando pwd
still executes in the working_dir
location, even though we change directories on the host machine:
lando pwd
# /var/www
cd subdir && lando pwd
# /var/www
3. COPY & ADD considerations
While you can use the COPY
and ADD
instructions in various places in the l337
service we recommend you use our context
convention instead. The tl;dr
there is it will be more performant.
The full explanation is that if Lando detects a COPY
or ADD
instruction it will automatically copy the entire application root into the build context. So if you have a BIG APP this can drastically slow build performance.
If for some reason you need COPY/ADD
to work eg you are basing your service on some preexisting Dockerfile with some build assets then we recommend you copy the build assets and Dockerfile into a subfolder and build from there. In this case only the files in that subfolder will be used and not the entire application.
Examples
If you would like to look at concrete and tested examples you can check out the below: