Skip to main content

Dockerize your APEX development environment

Nowadays Docker is everywhere. It is one of the main components of Continuous Integration / Continuous Development environments. That alone indicates Docker has to be seen more as a Software Delivery Platform than as a replacement of a virtual machine.

However ...

If you are running an Oracle database using Docker on your local machine to develop some APEX application, you will probably not move that container is a whole to test and production environments. Because in that case you would not only deliver a new APEX application to the production environment - which is a good thing - but also overwrite the data in production with the data from your development environment. And that won't make your users very excited.
So in this set up you will be using Docker as a replacement of a Virtual Machine and not as a Delivery Platform.
And that's exactly the way Martin is using it as he described in this recent blog post. It is an ideal way to get up and running with an Oracle database in a Docker container in a very short time (you have to download 3.5 Gb, so dependent on your connection that might take some time). This works fine for a short PoC. But what if you want to be sure your container lives a bit longer than a week or two? Because, if your container gets blown away for some sort of reason, you will lose all your data. The original image is still there, but that contains just the starting situation. A.k.a. an empty database. And - from recent experience - I can tell you that containers can get destroyed (in this case by an upgrade of the Docker software). The whole concept of containers is that they are ephemeral, lasting for a short period of time.

But luckily we can set up a Docker container in a way we are sure our data is safe, even when the container is destroyed. We can use volume mapping for that. So we don't need to download the complete installed database from the link Martin mentioned in his blog, but we need the Docker build files and the required Oracle database software and build the image ourselves. Exactly as Maria describes in her blogpost.
In my setup, once the image was created, I started the container by issuing this command:
docker run --name oracle -p 1521:1521 -p 5500:5500 \ -v /Users/Roel/docker/database:/opt/oracle/oradata \ oracle/database:12.2.0.1-ee
The advantage is all my data is stored outside my container, on my local machine. The container itself is static - there will be no changes there. So if the container blows up, I can just spin up a new one using the same data that is still stored on my disk. And it seems a bit faster as well ....

But if we want to develop APEX applications, we need to have ORDS installed somewhere as well. Of course we could just run ORDS locally connecting to the (forwarded) port 1521. But it would be way nicer if ORDS would run in a Docker container as well!
Before we start that second container, we have to make sure both containers are able to connect to each other. It is possible using the --link switch, but that is deprecated. The new way of connecting containers together, is to create a Docker network:
docker network create my_network
Then we add our running database container, named "oracle", to that network:
docker network connect my_network oracle
And now we can go looking for a Docker image for our ORDS. If you issue the command:
docker search ords
you'll get a few hits. As we already have a database running, we need an image just containing ORDS and fire that up - in this case the image lucassampsouza/ords_apex:3.0.9.
docker run -t -i \ --name ords \ --network=my_network \ -e DATABASE_HOSTNAME="oracle" \ -e DATABASE_PORT="1521" \ -e DATABASE_SERVICENAME="ORCLPDB1" \ -e DATABASE_PUBLIC_USER_PASS=oracle \ -e APEX_LISTENER_PASS=oracle \ -e APEX_REST_PASS=oracle \ -e ORDS_PASS=oracle \ --volume /Users/Roel/docker/apex/images:/usr/local/tomcat/webapps/i \ -p 8080:8080 \ lucassampsouza/ords_apex:3.0.9
It is version 3.0.9, but of course you can upgrade it yourself - or just wait for a newer version.
Some remarks about this command:
  • The --network switch adds this new container to the network immediately. 
  • I can reference the hostname of the database - normally the name or IP-address of the machine where the database is running on - by the name of the container, "oracle".
  • I defined (another) volume mapping for this container in a way my APEX images directory is located on my local machine. Thus I can easily patch or update APEX without touching the container. If this container gets blown away ... I just fire up a new one with this same command and I am ready to go.
So now we have two containers running next to each other. Nice. But now I need a solution for printing as well. So I asked Dimitri whether he has a Docker image of his APEX Office Print (AOP) solution. And he was kind enough to make one available for me. So very similar to the ORDS one, I started this one up, attached to that same network - and also with another volume mapping to a directory that holds my license key:
docker run -d \ --name apexofficeprint \ --network=my_network \ -p 8010:8010 \ -v /Users/Roel/docker/apexofficeprint/:/apexofficeprintstartup/ \ apexrnd/apexofficeprint \ -s /apexofficeprintstartup/
So now I only needed two more steps to make it working. First define the AOP URL in the component settings of AOP in APEX: http://apexofficeprint:8010/.(again notice the use of the container name in that URL). And finally open up the ACL for the APEX owner, so it is possible to connect from within the database to the AOP container:
begin dbms_network_acl_admin.append_host_ace ( host => 'apexofficeprint', lower_port => 8010, upper_port => 8010, ace => xs$ace_type(privilege_list => xs$name_list('http'), principal_name => 'APEX_050100', principal_type => xs_acl.ptype_db)); end;
So this complete configuration is totally ephemeral (love using that word again) as all important data is stored on my local disk. And I can easily burn or replace containers - for instance when a new version of the ORDS or AOP image comes available.
To end up, this is how it looks as a picture:
Now go and try it yourself!

Comments

Popular posts from this blog

Filtering in the APEX Interactive Grid

Remember Oracle Forms? One of the nice features of Forms was the use of GLOBAL items. More or less comparable to Application Items in APEX. These GLOBALS where often used to pre-query data. For example you queried Employee 200 in Form A, then opened Form B and on opening that Form the Employee field is filled with that (GLOBAL) value of 200 and the query was executed. So without additional keys strokes or entering data, when switching to another Form a user would immediately see the data in the same context. And they loved that. In APEX you can create a similar experience using Application Items (or an Item on the Global Page) for Classic Reports (by setting a Default Value to a Search Item) and Interactive Reports (using the  APEX_IR.ADD_FILTER  procedure). But what about the Interactive Grid? There is no APEX_IG package ... so the first thing we have to figure out is how can we set a filter programmatically? Start with creating an Interactive Grid based upon the good old Employ

apex_application.g_f0x array processing in Oracle 12

If you created your own "updatable reports" or your custom version of tabular forms in Oracle Application Express, you'll end up with a query that looks similar to this one: then you disable the " Escape special characters " property and the result is an updatable multirecord form. That was easy, right? But now we need to process the changes in the Ename column when the form is submitted, but only if the checkbox is checked. All the columns are submitted as separated arrays, named apex_application.g_f0x - where the "x" is the value of the "p_idx" parameter you specified in the apex_item calls. So we have apex_application.g_f01, g_f02 and g_f03. But then you discover APEX has the oddity that the "checkbox" array only contains values for the checked rows. Thus if you just check "Jones", the length of g_f02 is 1 and it contains only the empno of Jones - while the other two arrays will contain all (14) rows. So for

Stop using validations for checking constraints !

 If you run your APEX application - like a Form based on the EMP table - and test if you can change the value of Department to something else then the standard values of 10, 20, 30 or 40, you'll get a nice error message like this: But it isn't really nice, is it? So what do a lot of developers do? They create a validation (just) in order to show a nicer, better worded, error message like "This is not a valid department".  And what you then just did is writing code twice : Once in the database as a (foreign key) check constraint and once as a sql statement in your validation. And we all know : writing code twice is usually not a good idea - and executing the same query twice is not enhancing your performance! So how can we transform that ugly error message into something nice? By combining two APEX features: the Error Handling Function and the Text Messages! Start with copying the example of an Error Handling Function from the APEX documentation. Create this function