Why && rather than a new RUN - shell

In Dockerfiles I'm seeing most people using this syntax
RUN apt-get -y update \
&& apt-get install -y libicu-dev
over this one
RUN apt-get -y update
RUN apt-get install -y libicu-dev
For me the first one gets only one line (layer) cached while the second caches both (am I wrong ?) and stops as soon as a command is not successful.
Besides I don't find the first one more readable.
So why would we use the first syntax ?

It is optimisation for docker image layer. I also recommend to read Best practices for writing Dockerfiles
There is also interesting presentation from DockerCon EU 2017.

Lesser the layers, better the image.
Hence, combining commands using && will create a single layer.
Having two RUN will create two layers.

According to the images and layers documentation
Each layer is only a set of differences from the layer before it
So for example 2 layers creating different files would not use more disk space. Especially since Docker 17.05 allows multi-stage builds. However, it still could use more space if the second one is entirely modifying files from the first one.
Following Khapov Igor's comment I found out the real answer to the original question in the best practice doc:
Using apt-get update alone in a RUN statement causes caching issues and subsequent apt-get install instructions fail.
It's actually more about layer dependencies with previous commands for which results can evolve over time like apt-get update.
That's why they are telling:
Always combine RUN apt-get update with apt-get install in the same RUN statement

Each command in a Dockerfile creates another image layer.
Combining commands is a way to end up with less layers overall.
See https://docs.docker.com/engine/userguide/storagedriver/imagesandcontainers/#images-and-layers

This line:
RUN apt-get -y update \
&& apt-get install -y libicu-dev
will create one single docker layer and these lines:
RUN apt-get -y update
RUN apt-get install -y libicu-dev
will create two different layers.
This is the main reason why when you need to install something in your docker machine (ex: via APT) you tend to keep everything in one single line (aka layer)

As the other answers already said, every command generates a layer and it's usually desirable to have the minimum amount of layers per image.
Each layer is only a set of differences from the layer before it. The layers are stacked on top of each other. When you create a new container, you add a new writable layer on top of the underlying layers.
This means that unless you're going to "squash" your image (which translates in using the --squash option during the build), you end up having an image consuming space for nothing.
Example
# Dockerfile
FROM ubuntu
RUN apt-get update
RUN apt-get install -y --no-install-recommends dnsutils
RUN echo $( dig somewhere.nowhere )
RUN apt-get remove --purge dnsutils
RUN rm -rf /var/lib/apt/lists/*
COPY magicalScript.sh /
CMD /magicalScript.sh
In this case you'll have layers containing only overhead:
1 with cache coming from apt-get update
1 with dnsutils installed,
1 containing the removal of the dnsutils
1 containing the removal of the cache
The problem is that all those layers remain there and consume space for no reason at all.
Why squash is not always a good solution? Because the layers represents a cache as well. And it's extremely useful when you need to perform a lot of builds and you need them to be as fast as possible.
Usually it's good practice to group together operation related the installation of new packages on the OS:
# Dockerfile
FROM ubuntu
RUN useradd docker \
&& mkdir /home/docker \
&& chown docker:docker /home/docker \
&& addgroup docker staff
RUN apt-get update \
&& apt-get install -y --no-install-recommends ed less locales vim-tiny wget ca-certificates fonts-texgyre \
&& rm -rf /var/lib/apt/lists/*
RUN echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen \
&& locale-gen en_US.utf8 \
&& /usr/sbin/update-locale LANG=en_US.UTF-8
CMD ["mySpecialCommand"]

Related

How to make apt assume yes and force yes for all installations in a bash script

I'm currently getting into linux and want to write a bash script which sets up a new machine just the way I want it to be.
In order to do that I want to install differnt things on it etc.
What I'm trying to achieve here is to have a setting at the top of the bash script which will make apt accept all [y/n] questions asked during the execution of the script
Question example I want to automatically accept:
After this operation, 1092 kB of additional disk space will be used. Do you want to continue? [Y/n]
I just started creating the file so here is what i have so far:
#!/bin/bash
# Constants
# Set apt to accept all [y/n] questions
>> some setting here <<
# Update and upgrade apt
apt update;
apt full-upgrade;
# Install terminator
apt install terminator
apt is meant to be used interactively. If you want to automate things, look at apt-get, and in particular its -y option:
-y, --yes, --assume-yes
Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. If an undesirable
situation, such as changing a held package, trying to install an
unauthenticated package or removing an essential package occurs then
apt-get will abort. Configuration Item: APT::Get::Assume-Yes.
See also man apt-get for many more options.
With apt:
apt -o Apt::Get::Assume-Yes=true install <package>
See: man apt and man apt.conf
If you indeed want to set it up once at the top of the file as you say and then forget about it, you can use the APT_CONFIG environment variable. See apt.conf.
echo "APT::Get::Assume-Yes=yes" > /tmp/_tmp_apt.conf
export APT_CONFIG=/tmp/_tmp_apt.conf
apt-get update
apt-get install terminator
...
You can set up API assume yes permanently as follow:
echo "APT::Get::Assume-Yes \"true\";\nAPT::Get::allow \"true\";" | sudo tee -a /etc/apt/apt.conf.d/90_no_prompt
Another easy way to set it at the top of the your script is to use the command alias apt-get="apt-get --assume-yes", which causes all subsequent invocations of apt-get to include the --assume-yes argument. For example apt-get upgrade would automatically get converted to apt-get --assume-yes upgrade" by bash.
Please note, that this may cause errors, because some apt-get subcommands do not accept the --assume-yes argument. For example apt-get help would be converted to apt-get --assume-yes help which returns an error, because the help subcommand can't be used together with --assume-yes.

How to Test a Bash File in Terminal

I've been trying to make a bash file for newbie Linux users and I wanted to know if there is a way to test the bash file before running it.
Can I just see the result of my bash file in the terminal and not actually run it?
For example, I don't want to actually update and upgrade my system when I run this script, I just want to see the result of my bash file, whether it gives me back some error or not.
Wanted to know if there is a way to just see the result, like see the result of my 'echo' commands and etc.
echo ---------------
echo hello and welcome to the automized bash file for your new linux distro!
echo ---------------
sudo apt-get update -y ; sudo apt-get upgrade -y ; sudo apt-get autoremove -y ; sudo apt-get autoclean -y ; sudo apt-get clean -y
echo ---------------
echo as you were drinking your coffee,
echo your linux distro got updated, and autocleaned as well!
Thanks in advance!
To see the results of running a bash file, a bash interpreter would have to interpret it. So the simple answer would be no.
However, if you are willing to use an online tool, you could run a bash script online. In this manner, you can see the results of running a bash script, without ever having to run it on your own machine.
A google search popped up these ones, but I cannot vouch for their legitimacy:
https://www.jdoodle.com/test-bash-shell-script-online/ (for evaluating the results of a script)
https://www.shellcheck.net/ (for assessing shell code quality)
There's no general way to run a shell script without running it. You can sometimes sort-of modify the script to make it go through the motions without actually doing anything significant, but this requires understanding the script and the commands in it.
For example, in the update script in the question, you could just add echo before each sudo apt-get command, something like this (note that I've reformatted it a bit, and added quotes around some fixed strings):
echo '---------------'
echo 'hello and welcome to the automized bash file for your new linux distro!'
echo '---------------'
echo sudo apt-get update -y
echo sudo apt-get upgrade -y
echo sudo apt-get autoremove -y
...etc...
This will simply print the commands, rather than executing them. (Note: if any commands had redirections, e.g. somecommand >outputfile or somecommand | anothercommand, the adding echo doesn't remove the redirection, so you'll need to make other changes as well).
If you want to actually see what the various apt-get commands would do if you ran them... you're in luck, because apt-get happens to have a --dry-run option (see the man page and this AskUbuntu question).
Note that this is a feature specific to apt-get. Very few shell commands have an option like this, so it's not like some sort of universal just-try-it-out switch. In fact, not even all apt-get subcommands support --dry-run.
Most relevantly, apt-get update doesn't support --dry-run! And it wouldn't be useful if it did. If you don't start by updating the package indexes -- actually updating them, not just pretending to -- then the other apt-get commands won't be able to tell what's new, and won't actually tell you what needs to be changed.
If you don't actually-for-real update the indexes, then you can't tell what the rest of the script would do if it ran for real. So you could do something like this:
...
sudo apt-get update -y
sudo apt-get upgrade --dry-run --assume-no
sudo apt-get autoremove --dry-run --assume-no
...etc...
...but be aware the script is actually executing, and while some of its effects have been disabled, others haven't.

Just to Update and Upgrade in terminal

so what i want to do is to create a shell script to update and upgrade for that i just created an .sh file in which there are three lines of command
sudo apt-get update
sudo apt-get upgrade
sudo apt-get dist-upgrade
after running .sh file it executes just fine.
but what i want to do more with it is - after successfully running update command and while running upgrade command it ask us to get the archive with 'Y/N'.
can i do something in .sh so that i don't have to type 'y' or 'n' . i want y to be default.
On a debian based installation on Raspberry i using the short form of IF THEN ELSE like this...
apt update && apt -y full-upgrade || echo 'Hm, something failed!'
...and "The Matrix" asking nothing.

I have made a dockerfile and I was going to run it on AWS ECS but I cant as it requires -t

Here is my docker run and the docker file is there a reason why it requires -t and isnt working on ECS thanks for any help. I dont
understand what -t does so if someone could also help with that thanks.
This is just a basic docker that connects to my rds and uses wordpress. I dont have any plugins and shapely is the theme i'm using .
command docker run -t --name wordpress -d -p 80:80 dockcore/wordpress
FROM ubuntu
#pt-get clean all
RUN apt-get -y update
RUN DEBIAN_FRONTEND=noninteractive apt-get -y install unzip wget mysql-client mysql-server apache2 libapache2-mod-php7.0 pwgen python-setuptools vim-tiny php7.0-mysql php7.0-lda
RUN rm -fr /var/cashe/*files neeeded
ADD wordpress.conf /etc/apache2/sites-enabled/000-default.conf
# Wordpress install
RUN wget -P /var/www/html/ https://wordpress.org/latest.zip
RUN unzip /var/www/html/latest.zip -d /var/www/html/
RUN rm -fr /var/www/html/latest.zip
# Copy he wp config file
RUN cp /var/www/html/wordpress/wp-config-sample.php /var/www/html/wordpress/wp-config.php
# Expose web port
EXPOSE 80
# wp config for database
RUN sed -ie 's/database_name_here/wordpress/g' /var/www/html/wordpress/wp-config.php
RUN sed -ie 's/username_here/root/g' /var/www/html/wordpress/wp-config.php
RUN sed -ie 's/password_here/password/g' /var/www/html/wordpress/wp-config.php
RUN sed -ie 's/localhost/wordpressrds.xxxxxxxxxxxxxx.ap-southeast-2.rds.amazonaws.com:3306/g' /var/www/html/wordpress/wp-config.php
RUN rm -fr /var/www/html/wordpress/wp-content/themes/*
RUN rm -fr /var/www/html/wordpress/wp-content/plugins/*
ADD /shapely /var/www/html/wordpress/wp-content/themes/
# Start apache on boot
RUN echo "service apache2 start" >> ~/.bashrc
I see a couple problems. First of all your container should never require -t in order to run unless it is a temporary container that you plan to interact with using a shell. Background containers shouldn't require an interactive TTY interface, they just run in the background autonomously.
Second in your docker file I see a lot of RUN statements which are basically the build time commands for setting up the initial state of the container, but you don't have any CMD statement.
You need a CMD which is the process to actually kick off and start in the container when you try to run the container. RUN statements only execute once during the initial docker build, and then the results of those run statements are saved into the container image. When you run a docker container it has the initial state that was setup by the RUN statements, and then the CMD statement kicks off a running process in the container.
So it looks like that last RUN in your Dockerfile should be a CMD since the Apache server is the long running process that you want to run with the container state that you previously setup using all those RUN statements.
Another thing you should do is chain many of those consecutive RUN statements into one. Docker creates a separate layer for each RUN command, where each layer is kind of like a Git commit of the state of the container. So it is very wasteful to have so many RUN statements because it makes way too many container layers. You can do something like this ot chain RUN statements together instead to make a smaller, more efficient container:
RUN apt-get -y update && \
DEBIAN_FRONTEND=noninteractive apt-get -y install unzip wget mysql-client mysql-server apache2 libapache2-mod-php7.0 pwgen python-setuptools vim-tiny php7.0-mysql php7.0-lda && \
rm -fr /var/cashe/*files neeeded
I recommend reading through this guide from Docker that covers best practices for writing a Dockerfile: https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#cmd

How to detect whether apt-get requires a reboot using Bash?

I am writing a bash script (for apt-get based OS's) that automates the installations process of various programs. In this process I run "apt-get -fy update" and "apt-get -fy upgrade" sometimes. In the process of upgrading, occasionally, a restart is required.
My question: is there a way of testing if the system is asking for a restart after running "apt-get -fy upgrade"? I am trying to write the script for it to run from beginning to end without human any intervention.
Thank you.
Use the file /var/run/reboot-required which does exactly what you want. So we will have this:
apt-get update && apt-get -fy upgrade && [ -f /var/run/reboot-required ] && shutdown -r now
I don't recall whether apt-get actually gives you a predictably formatted message informing you whether a restart is necessary, but if it does you could just check the output, e.g. something like apt-get -fy update | grep -q 'fill in restart message pattern' && reboot.
Another probably less reliable alternative is to use checkrestart from the debian-goodies package.
If you do a
apt-get -fy update && shutdown -r now
it will respect the order and will update until finish and finally restart your server.

Resources