3

Unable to execute the CMD command on docker for windows in the dockerfile.

I want to run couple of commands using the CMD, however, unable to run even a single command.

I have tried the follwoing, and nothing works...

1.

CMD "sqlcmd -S database -i C:\db\db_scripts\upgradescript.sql -U sa -P mypassword"

2.

RUN ["powershell", "-NoProfile", "-Command", "c:\db\db_scripts\script.ps1"]

3.

CMD ["cmd", "sqlcmd", "-S", "database", "-i", "C:\db\db_scripts\upgradescript.sql", "-U", "sa", "-P", "mypassword"]

4.

ARG arg1=database
ARG arg2=C:\db\db_scripts\upgradescript.sql
ARG arg3=sa
ARG arg4=mypassword!

RUN ["cmd", "-NoProfile", "-Command", "sqlcmd -S $env:arg1 -i $env:arg2 -U $env:arg3 -P $env:arg4"]

5.

RUN powershell.exe  c:\db\db_scripts\script.ps1

6.

ENTRYPOINT ["powershell", "-NoProfile", "-Command", "sqlcmd"]
CMD ["-S database -i C:\db\db_scripts\upgradescript.sql -U sa -P mypassword"]

SUPPORTING FILES:

A. Script.ps1 (Used by 2 and 5)

cmd /c "sqlcmd -S database -i C:\db\db_scripts\upgradescript.sql -U sa -P mypassword"

B. Dockerfile

# escape=`

FROM microsoft/mssql-server-windows-developer
COPY db\* c:\db\
COPY db_scripts\* c:\db\db_scripts\
ENV attach_dbs="[{'dbName':'ABC','dbFiles':['C:\db\ABC.mdf','C:\db\ABC_Log.ldf']},{'dbName':'XYZ','dbFiles':['C:\db\XYZ.mdf','C:\db\XYZ_Log.ldf']}]"
CMD "sqlcmd -S database -i C:\db\db_scripts\upgradescript.sql -U sa -P mypassword"

Please note, I do not need to use "\\" because of the escape character.

OBSERVATIONS

The container starts and disappears after a few seconds.

If I remove the CMD part, the containers work just fine. I can get into the container and can run the above sqlcmd command in cmd shell.

What am I doing wrong, what's missing, any better approach etc. Could anyone give some guidance please? Thanks!

Edit:

Based on Josh's answer, the only way this ENTRYPOINT command is working for me is (sharing so others can benefit or even post better way of doing this)...

ENV arg1 database
ENV arg2 C:\db\db_scripts\upgradescript.sql
ENV arg3 sa
ENV arg4 mypassword
ENTRYPOINT ["powershell sleep(60); sqlcmd"]
CMD ["-S $env:arg1 -i $env:arg2 -U $env:arg3 -P $env:arg4"]
wafers
  • 1,149
  • 4
  • 14
  • 34

1 Answers1

8

I think you may be misunderstanding the nature of the CMD versus ENTRYPOINT vs RUN instruction. Both CMD and ENTRYPOINT take effect at container startup/runtime, whereas RUN is a build-time command. Somewhat confusingly, you use RUN to prepare your container image during the docker build process, while the CMD is what gets run in the container.

via the documentation,

The main purpose of a CMD is to provide defaults for an executing container...

Any given container image will only ever have a single CMD instruction.

There can only be one CMD instruction in a Dockerfile. If you list more than one CMD then only the last CMD will take effect.

If you want to make sure that an executable runs at container start, then you might consider using the ENTRYPOINT instruction; think of ENTRYPOINT as being a command that supplies defaults to the CMD instruction. From the docs:

Dockerfile should specify at least one of CMD or ENTRYPOINT commands. ENTRYPOINT should be defined when using the container as an executable. CMD should be used as a way of defining default arguments for an ENTRYPOINT command or for executing an ad-hoc command in a container. CMD will be overridden when running the container with alternative arguments.

The RUN command is a build-time instruction that you use to prepare your container image with necessary files, configuration, etc.

See this SO question for more on ENTRYPOINT vs CMD.

ED: The case you're looking at, running a SQL script in the container, is a tricky one because the script must not run before the database engine is ready to accept commands. You could run the upgrade script in the ENTRYPOINT instruction as a first step. You may also consider running the SQL upgrade script post-container provisioning, via a docker exec command.

ED2: The example provided:

ENTRYPOINT ["powershell", "-NoProfile", "-Command", "sqlcmd"] CMD ["sqlcmd -S database -i C:\db\db_scripts\upgradescript.sql -U sa -P mypassword"]

does not work because it results in what is probably a nonsense command-line instruction. Remember that the CMD supplies parameters to the ENTRYPOINT instruction, so you'll only specify the powershell sqlcmd in the ENTRYPOINT, leaving CMD to supply the parameters (this is untested, OTOMH):

ENTRYPOINT powershell sqlcmd -S database -U sa -P mypassword -i

CMD C:\db\db_scripts\upgradescript.sql

ED 3: By combining the two statements and adding a statement terminator (;) to the ENTRYPOINT, you could then allow the standard SQL container .\Start.ps1 script to run, which enters an infinite loop that prevents the container from stopping immediately (containers run only as long as the executed process in them is running). This guarantees that your startup script is executed, even if the user overrides the CMD instruction at runtime:

ENTRYPOINT powershell "sqlcmd -S database -U sa -P mypassword -i 'C:\db\db_scripts\upgradescript.sql';"

CMD .\Start.ps1

Josh E
  • 7,390
  • 2
  • 32
  • 44
  • Excellent description! I did actually try with Entrypoint which I forgot to mention, and have added as 6th bullet. It did not work either. WIll look more into it. If you think my syntax or approach is incorrect, please guide. Thanks! – wafers Oct 09 '18 at 14:40
  • I added a potential approach to try – Josh E Oct 09 '18 at 14:55
  • If my answer has a reason for downvoting, it would be helpful for me to understand what I got wrong so I can learn and improve for the future. thanks – Josh E Oct 09 '18 at 15:15
  • your answer is great – user2915097 Oct 09 '18 at 15:39
  • As I said, it's very good answer, I am just testing different combinations of Entrypoint, once got it working, will update the ED2 or add ED3 as direct answer. Thanks again Josh! – wafers Oct 09 '18 at 15:54
  • Thanks. I've added an addition that describes combining the two – Josh E Oct 09 '18 at 16:44
  • @JoshE, Well, the way ENTRYPOINT was working for me, I have updated the answer. However, you were right, I am unable to acheicve what I wanted to because the script ran too early. The server is not read. Eventhough I added sleeping but it didn't work either. – wafers Oct 09 '18 at 17:15
  • I've moved your edit from this answer into your original question to avoid confusion. An option you could look at would be having a powershell script execute in ENTRYPOINT that schedules a one-time job to run as soon as the sql server is ready to accept commands – Josh E Oct 09 '18 at 17:21
  • @JoshE, your ED3 `ENTRYPOINT` example works as well. I have to implement some logic in the powershell script to perform the upgrade, but that wasn't the question anyway :-). Many thanks! – wafers Oct 10 '18 at 09:30